Merge changes from topic "kt-has" into androidx-main

* changes:
  Update fragment module with HasDefaultViewModelProviderFactory
  Update navigation module with HasDefaultViewModelProviderFactory
  Convert HasDefaultViewModelProviderFactory to Kotlin
  Rename HasDefaultViewModelProviderFactory.java to .kt
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index f5a9b59..36c6362 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,5 +1,5 @@
 [Hook Scripts]
-checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT} -c ${REPO_ROOT}/frameworks/support/development/checkstyle/config/support-lib.xml -p development/checkstyle/prebuilt/com.android.support.checkstyle.jar
+checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
 ktlint_hook = ${REPO_ROOT}/frameworks/support/development/ktlint.sh --skip-if-empty --file=${PREUPLOAD_FILES_PREFIXED}
 warn_check_api = ${REPO_ROOT}/frameworks/support/development/apilint.py -f ${PREUPLOAD_FILES}
 
diff --git a/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/experimental/UseKtExperimentalFromJava.java b/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/experimental/UseKtExperimentalFromJava.java
index 6363203..0a06ea2 100644
--- a/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/experimental/UseKtExperimentalFromJava.java
+++ b/annotation/annotation-experimental-lint/integration-tests/src/main/java/sample/experimental/UseKtExperimentalFromJava.java
@@ -100,8 +100,4 @@
     @SuppressWarnings("deprecation")
     @androidx.annotation.experimental.UseExperimental(markerClass = ExperimentalDateTimeKt.class)
     static class FancyDateProvider extends DateProviderKt {}
-
-    @SuppressWarnings("deprecation")
-    @kotlin.UseExperimental(markerClass = ExperimentalDateTimeKt.class)
-    static class FancyDateProvider2 extends DateProviderKt {}
 }
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesSyncToFrameworkTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesSyncToFrameworkTestCase.kt
index 5bd48ef..ac32070 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesSyncToFrameworkTestCase.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/LocalesSyncToFrameworkTestCase.kt
@@ -22,7 +22,6 @@
 import android.content.Intent
 import android.content.pm.PackageManager
 import android.os.LocaleList
-import androidx.annotation.RequiresApi
 import androidx.appcompat.testutils.LocalesActivityTestRule
 import androidx.appcompat.testutils.LocalesUtils.CUSTOM_LOCALE_LIST
 import androidx.appcompat.testutils.LocalesUtils.assertConfigurationLocalesEquals
@@ -35,7 +34,6 @@
 import junit.framework.Assert.assertNull
 import org.junit.After
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -54,7 +52,6 @@
     private lateinit var appLocalesComponent: ComponentName
     private val instrumentation = InstrumentationRegistry.getInstrumentation()
 
-    @RequiresApi(33)
     @Before
     fun setUp() {
         // setting the app to follow system.
@@ -78,10 +75,7 @@
         )
     }
 
-    @Ignore // b/264589466
-    @RequiresApi(33)
     @Test
-    @SdkSuppress(maxSdkVersion = 33) // b/262909049: Failing on SDK 34
     fun testAutoSync_preTToPostT_syncsSuccessfully() {
         val firstActivity = rule.activity
 
@@ -140,7 +134,6 @@
     }
 
     @After
-    @RequiresApi(33)
     fun teardown() {
         val context = instrumentation.context
 
@@ -163,4 +156,4 @@
             /* flags= */ PackageManager.DONT_KILL_APP
         )
     }
-}
\ No newline at end of file
+}
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
index e011d02..f8c6226 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/AlwaysSupportedFeatures.java
@@ -43,7 +43,7 @@
             case Features.JOIN_SPEC_AND_QUALIFIED_ID:
                 // fall through
             case Features.NUMERIC_SEARCH:
-                //fall through
+                // fall through
             case Features.VERBATIM_SEARCH:
                 // fall through
             case Features.SEARCH_RESULT_MATCH_INFO_SUBMATCH:
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
index 6ac3760..9e70974 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/FeaturesImpl.java
@@ -57,13 +57,15 @@
                 // synced over into service-appsearch.
                 // fall through
             case SEARCH_SPEC_ADVANCED_RANKING_EXPRESSION:
-                // TODO(b/261474063): Update to reflect support in Android U+ once advanced
+                // TODO(b/261474063) : Update to reflect support in Android U+ once advanced
                 //  ranking becomes available.
                 // fall through
             case Features.JOIN_SPEC_AND_QUALIFIED_ID:
                 // TODO(b/256022027) : Update to reflect support in Android U+ once this feature is
+                // synced over into service-appsearch.
+                // fall through
             case Features.VERBATIM_SEARCH:
-                // TODO(b/204333391): Update to reflect support in Android U+ once this feature is
+                // TODO(b/204333391) : Update to reflect support in Android U+ once this feature is
                 // synced over into service-appsearch.
                 return false;
             default:
diff --git a/appsearch/appsearch/api/current.txt b/appsearch/appsearch/api/current.txt
index 98635d6..f747bf5 100644
--- a/appsearch/appsearch/api/current.txt
+++ b/appsearch/appsearch/api/current.txt
@@ -523,7 +523,7 @@
     method public androidx.appsearch.app.SearchSpec.Builder addProjectionPathsForDocumentClass(Class<?>, java.util.Collection<androidx.appsearch.app.PropertyPath!>) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.SearchSpec.Builder addProjectionsForDocumentClass(Class<?>, java.util.Collection<java.lang.String!>) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.SearchSpec build();
-    method public androidx.appsearch.app.SearchSpec.Builder setJoinSpec(androidx.appsearch.app.JoinSpec);
+    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 public androidx.appsearch.app.SearchSpec.Builder setMaxSnippetSize(@IntRange(from=0, to=0x2710) int);
     method public androidx.appsearch.app.SearchSpec.Builder setOrder(int);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_PROPERTY_WEIGHTS) public androidx.appsearch.app.SearchSpec.Builder setPropertyWeightPaths(String, java.util.Map<androidx.appsearch.app.PropertyPath!,java.lang.Double!>);
diff --git a/appsearch/appsearch/api/public_plus_experimental_current.txt b/appsearch/appsearch/api/public_plus_experimental_current.txt
index 98635d6..f747bf5 100644
--- a/appsearch/appsearch/api/public_plus_experimental_current.txt
+++ b/appsearch/appsearch/api/public_plus_experimental_current.txt
@@ -523,7 +523,7 @@
     method public androidx.appsearch.app.SearchSpec.Builder addProjectionPathsForDocumentClass(Class<?>, java.util.Collection<androidx.appsearch.app.PropertyPath!>) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.SearchSpec.Builder addProjectionsForDocumentClass(Class<?>, java.util.Collection<java.lang.String!>) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.SearchSpec build();
-    method public androidx.appsearch.app.SearchSpec.Builder setJoinSpec(androidx.appsearch.app.JoinSpec);
+    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 public androidx.appsearch.app.SearchSpec.Builder setMaxSnippetSize(@IntRange(from=0, to=0x2710) int);
     method public androidx.appsearch.app.SearchSpec.Builder setOrder(int);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_PROPERTY_WEIGHTS) public androidx.appsearch.app.SearchSpec.Builder setPropertyWeightPaths(String, java.util.Map<androidx.appsearch.app.PropertyPath!,java.lang.Double!>);
diff --git a/appsearch/appsearch/api/restricted_current.txt b/appsearch/appsearch/api/restricted_current.txt
index 98635d6..f747bf5 100644
--- a/appsearch/appsearch/api/restricted_current.txt
+++ b/appsearch/appsearch/api/restricted_current.txt
@@ -523,7 +523,7 @@
     method public androidx.appsearch.app.SearchSpec.Builder addProjectionPathsForDocumentClass(Class<?>, java.util.Collection<androidx.appsearch.app.PropertyPath!>) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.SearchSpec.Builder addProjectionsForDocumentClass(Class<?>, java.util.Collection<java.lang.String!>) throws androidx.appsearch.exceptions.AppSearchException;
     method public androidx.appsearch.app.SearchSpec build();
-    method public androidx.appsearch.app.SearchSpec.Builder setJoinSpec(androidx.appsearch.app.JoinSpec);
+    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 public androidx.appsearch.app.SearchSpec.Builder setMaxSnippetSize(@IntRange(from=0, to=0x2710) int);
     method public androidx.appsearch.app.SearchSpec.Builder setOrder(int);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.SEARCH_SPEC_PROPERTY_WEIGHTS) public androidx.appsearch.app.SearchSpec.Builder setPropertyWeightPaths(String, java.util.Map<androidx.appsearch.app.PropertyPath!,java.lang.Double!>);
diff --git a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/DocumentIdUtilCtsTest.java b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/DocumentIdUtilCtsTest.java
similarity index 95%
rename from appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/DocumentIdUtilCtsTest.java
rename to appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/DocumentIdUtilCtsTest.java
index eda327d..89d2180 100644
--- a/appsearch/appsearch/src/androidTest/java/androidx/appsearch/app/DocumentIdUtilCtsTest.java
+++ b/appsearch/appsearch/src/androidTest/java/androidx/appsearch/cts/app/DocumentIdUtilCtsTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * 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.
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-package androidx.appsearch.app;
+package androidx.appsearch.cts.app;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import androidx.appsearch.app.GenericDocument;
 import androidx.appsearch.util.DocumentIdUtil;
 
 import org.junit.Test;
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 e25c3a9..6a2a8f8 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
@@ -286,6 +286,7 @@
                 .build();
 
         SearchSpec searchSpec = new SearchSpec.Builder()
+                .setRankingStrategy(SearchSpec.RANKING_STRATEGY_JOIN_AGGREGATE_SCORE)
                 .setJoinSpec(joinSpec)
                 .build();
 
@@ -450,6 +451,17 @@
 
         assertThat(e.getMessage()).isEqualTo("Attempting to rank based on joined documents, but"
                 + " no JoinSpec provided");
+
+        e = assertThrows(IllegalStateException.class, () -> new SearchSpec.Builder()
+                .setRankingStrategy(SearchSpec.RANKING_STRATEGY_CREATION_TIMESTAMP)
+                .setJoinSpec(new JoinSpec.Builder("childProp")
+                        .setAggregationScoringStrategy(
+                                JoinSpec.AGGREGATION_SCORING_SUM_RANKING_SIGNAL)
+                        .build())
+                .build());
+        assertThat(e.getMessage()).isEqualTo("Aggregate scoring strategy has been set in the "
+                + "nested JoinSpec, but ranking strategy is not "
+                + "RANKING_STRATEGY_JOIN_AGGREGATE_SCORE");
     }
 
     @Test
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 dfcc149..8a05b92 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSchema.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSchema.java
@@ -570,14 +570,14 @@
         public static final int JOINABLE_VALUE_TYPE_NONE = 0;
 
         /**
-         * Content in this property will be used as string qualified id to join documents.
+         * Content in this string property will be used as a qualified id to join documents.
          * <ul>
          *     <li>Qualified id: a unique identifier for a document, and this joinable value type is
          *     similar to primary and foreign key in relational database. See
-         *     {@link androidx.appsearch.util.DocumentIdUtil} for more details.</li>
+         *     {@link androidx.appsearch.util.DocumentIdUtil} for more details.
          *     <li>Currently we only support single string joining, so it should only be used with
-         *     {@link PropertyConfig.Cardinality} other than
-         *     {@link PropertyConfig#CARDINALITY_REPEATED}.
+         *     {@link PropertyConfig#CARDINALITY_OPTIONAL} and
+         *     {@link PropertyConfig#CARDINALITY_REQUIRED}.
          * </ul>
          */
         // @exportToFramework:startStrip()
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
index bc891fe..d3fcbba 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/Features.java
@@ -104,8 +104,8 @@
 
     /**
      * Feature for {@link #isFeatureSupported(String)}. This feature covers
-     * {@link AppSearchSchema.StringPropertyConfig#JOINABLE_VALUE_TYPE_QUALIFIED_ID} and all other
-     * join features.
+     * {@link AppSearchSchema.StringPropertyConfig#JOINABLE_VALUE_TYPE_QUALIFIED_ID},
+     * {@link SearchSpec.Builder#setJoinSpec}, and all other join features.
      */
     String JOIN_SPEC_AND_QUALIFIED_ID = "JOIN_SPEC_AND_QUALIFIED_ID";
 
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 eb9a30c..1f92e9a 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchSpec.java
@@ -1071,6 +1071,11 @@
          *
          * @param joinSpec a specification on how to perform the Join operation.
          */
+        // @exportToFramework:startStrip()
+        @RequiresFeature(
+                enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
+                name = Features.JOIN_SPEC_AND_QUALIFIED_ID)
+        // @exportToFramework:endStrip()
         @NonNull
         public Builder setJoinSpec(@NonNull JoinSpec joinSpec) {
             resetIfBuilt();
@@ -1232,12 +1237,22 @@
          * @throws IllegalStateException if the ranking strategy is
          * {@link #RANKING_STRATEGY_JOIN_AGGREGATE_SCORE} and {@link #setJoinSpec} has never been
          * called.
+         * @throws IllegalStateException if the aggregation scoring strategy has been set in
+         * {@link JoinSpec#getAggregationScoringStrategy()} but the ranking strategy is not
+         * {@link #RANKING_STRATEGY_JOIN_AGGREGATE_SCORE}.
          *
          */
         @NonNull
         public SearchSpec build() {
             Bundle bundle = new Bundle();
             if (mJoinSpec != null) {
+                if (mRankingStrategy != RANKING_STRATEGY_JOIN_AGGREGATE_SCORE
+                        && mJoinSpec.getAggregationScoringStrategy()
+                        != JoinSpec.AGGREGATION_SCORING_OUTER_RESULT_RANKING_SIGNAL) {
+                    throw new IllegalStateException("Aggregate scoring strategy has been set in "
+                            + "the nested JoinSpec, but ranking strategy is not "
+                            + "RANKING_STRATEGY_JOIN_AGGREGATE_SCORE");
+                }
                 bundle.putBundle(JOIN_SPEC, mJoinSpec.getBundle());
             } else if (mRankingStrategy == RANKING_STRATEGY_JOIN_AGGREGATE_SCORE) {
                 throw new IllegalStateException("Attempting to rank based on joined documents, but "
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureSweepTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureSweepTest.kt
index bcb337f..6d15a0d 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureSweepTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureSweepTest.kt
@@ -22,7 +22,6 @@
 import androidx.benchmark.perfetto.PerfettoHelper
 import androidx.benchmark.perfetto.PerfettoHelper.Companion.LOWEST_BUNDLED_VERSION_SUPPORTED
 import androidx.benchmark.perfetto.PerfettoHelper.Companion.isAbiSupported
-import androidx.test.filters.FlakyTest
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
 import androidx.testutils.verifyWithPolling
@@ -38,6 +37,7 @@
 import org.junit.runners.Parameterized
 import kotlin.test.assertEquals
 import kotlin.test.fail
+import org.junit.Ignore
 
 /**
  * Trace validation tests for PerfettoCapture
@@ -61,12 +61,12 @@
         PerfettoHelper.stopAllPerfettoProcesses()
     }
 
-    @FlakyTest(bugId = 258216025)
+    @Ignore("b/258216025")
     @SdkSuppress(minSdkVersion = LOWEST_BUNDLED_VERSION_SUPPORTED, maxSdkVersion = 33)
     @Test
     fun captureAndValidateTrace_bundled() = captureAndValidateTrace(unbundled = false)
 
-    @FlakyTest(bugId = 258216025)
+    @Ignore("b/258216025")
     @Test
     @SdkSuppress(maxSdkVersion = 33) // b/262909049: Failing on SDK 34
     fun captureAndValidateTrace_unbundled() = captureAndValidateTrace(unbundled = true)
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoSdkHandshakeTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoSdkHandshakeTest.kt
index 368cacf..65ac589 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoSdkHandshakeTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoSdkHandshakeTest.kt
@@ -47,7 +47,7 @@
 import org.junit.runners.Parameterized
 import org.junit.runners.Parameterized.Parameters
 
-private const val tracingPerfettoVersion = "1.0.0-alpha09" // TODO(224510255): get by 'reflection'
+private const val tracingPerfettoVersion = "1.0.0-alpha10" // TODO(224510255): get by 'reflection'
 private const val minSupportedSdk = Build.VERSION_CODES.R // TODO(234351579): Support API < 30
 
 @RunWith(Parameterized::class)
diff --git a/browser/browser/api/1.5.0-beta02.txt b/browser/browser/api/1.5.0-beta02.txt
new file mode 100644
index 0000000..0598270
--- /dev/null
+++ b/browser/browser/api/1.5.0-beta02.txt
@@ -0,0 +1,469 @@
+// Signature format: 4.0
+package androidx.browser.browseractions {
+
+  @Deprecated public class BrowserActionItem {
+    ctor @Deprecated public BrowserActionItem(String, android.app.PendingIntent, @DrawableRes int);
+    ctor @Deprecated public BrowserActionItem(String, android.app.PendingIntent);
+    method @Deprecated public android.app.PendingIntent getAction();
+    method @Deprecated public int getIconId();
+    method @Deprecated public String getTitle();
+  }
+
+  @Deprecated public class BrowserActionsIntent {
+    method @Deprecated public static String? getCreatorPackageName(android.content.Intent);
+    method @Deprecated public android.content.Intent getIntent();
+    method @Deprecated public static String? getUntrustedCreatorPackageName(android.content.Intent);
+    method @Deprecated public static void launchIntent(android.content.Context, android.content.Intent);
+    method @Deprecated public static void openBrowserAction(android.content.Context, android.net.Uri);
+    method @Deprecated public static void openBrowserAction(android.content.Context, android.net.Uri, int, java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem!>, android.app.PendingIntent);
+    method @Deprecated public static java.util.List<androidx.browser.browseractions.BrowserActionItem!> parseBrowserActionItems(java.util.ArrayList<android.os.Bundle!>);
+    field @Deprecated public static final String ACTION_BROWSER_ACTIONS_OPEN = "androidx.browser.browseractions.browser_action_open";
+    field @Deprecated public static final String EXTRA_APP_ID = "androidx.browser.browseractions.APP_ID";
+    field @Deprecated public static final String EXTRA_MENU_ITEMS = "androidx.browser.browseractions.extra.MENU_ITEMS";
+    field @Deprecated public static final String EXTRA_SELECTED_ACTION_PENDING_INTENT = "androidx.browser.browseractions.extra.SELECTED_ACTION_PENDING_INTENT";
+    field @Deprecated public static final String EXTRA_TYPE = "androidx.browser.browseractions.extra.TYPE";
+    field @Deprecated public static final int ITEM_COPY = 3; // 0x3
+    field @Deprecated public static final int ITEM_DOWNLOAD = 2; // 0x2
+    field @Deprecated public static final int ITEM_INVALID_ITEM = -1; // 0xffffffff
+    field @Deprecated public static final int ITEM_OPEN_IN_INCOGNITO = 1; // 0x1
+    field @Deprecated public static final int ITEM_OPEN_IN_NEW_TAB = 0; // 0x0
+    field @Deprecated public static final int ITEM_SHARE = 4; // 0x4
+    field @Deprecated public static final String KEY_ACTION = "androidx.browser.browseractions.ACTION";
+    field @Deprecated public static final String KEY_ICON_ID = "androidx.browser.browseractions.ICON_ID";
+    field @Deprecated public static final String KEY_TITLE = "androidx.browser.browseractions.TITLE";
+    field @Deprecated public static final int MAX_CUSTOM_ITEMS = 5; // 0x5
+    field @Deprecated public static final int URL_TYPE_AUDIO = 3; // 0x3
+    field @Deprecated public static final int URL_TYPE_FILE = 4; // 0x4
+    field @Deprecated public static final int URL_TYPE_IMAGE = 1; // 0x1
+    field @Deprecated public static final int URL_TYPE_NONE = 0; // 0x0
+    field @Deprecated public static final int URL_TYPE_PLUGIN = 5; // 0x5
+    field @Deprecated public static final int URL_TYPE_VIDEO = 2; // 0x2
+  }
+
+  @Deprecated public static final class BrowserActionsIntent.Builder {
+    ctor @Deprecated public BrowserActionsIntent.Builder(android.content.Context, android.net.Uri);
+    method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent build();
+    method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder setCustomItems(java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem!>);
+    method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder setCustomItems(androidx.browser.browseractions.BrowserActionItem!...);
+    method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder setOnItemSelectedAction(android.app.PendingIntent);
+    method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder setUrlType(int);
+  }
+
+}
+
+package androidx.browser.customtabs {
+
+  public final class CustomTabColorSchemeParams {
+    field @ColorInt public final Integer? navigationBarColor;
+    field @ColorInt public final Integer? navigationBarDividerColor;
+    field @ColorInt public final Integer? secondaryToolbarColor;
+    field @ColorInt public final Integer? toolbarColor;
+  }
+
+  public static final class CustomTabColorSchemeParams.Builder {
+    ctor public CustomTabColorSchemeParams.Builder();
+    method public androidx.browser.customtabs.CustomTabColorSchemeParams build();
+    method public androidx.browser.customtabs.CustomTabColorSchemeParams.Builder setNavigationBarColor(@ColorInt int);
+    method public androidx.browser.customtabs.CustomTabColorSchemeParams.Builder setNavigationBarDividerColor(@ColorInt int);
+    method public androidx.browser.customtabs.CustomTabColorSchemeParams.Builder setSecondaryToolbarColor(@ColorInt int);
+    method public androidx.browser.customtabs.CustomTabColorSchemeParams.Builder setToolbarColor(@ColorInt int);
+  }
+
+  public class CustomTabsCallback {
+    ctor public CustomTabsCallback();
+    method public void extraCallback(String, android.os.Bundle?);
+    method public android.os.Bundle? extraCallbackWithResult(String, android.os.Bundle?);
+    method public void onActivityResized(@Dimension(unit=androidx.annotation.Dimension.PX) int, @Dimension(unit=androidx.annotation.Dimension.PX) int, android.os.Bundle);
+    method public void onMessageChannelReady(android.os.Bundle?);
+    method public void onNavigationEvent(int, android.os.Bundle?);
+    method public void onPostMessage(String, android.os.Bundle?);
+    method public void onRelationshipValidationResult(@androidx.browser.customtabs.CustomTabsService.Relation int, android.net.Uri, boolean, android.os.Bundle?);
+    field public static final int NAVIGATION_ABORTED = 4; // 0x4
+    field public static final int NAVIGATION_FAILED = 3; // 0x3
+    field public static final int NAVIGATION_FINISHED = 2; // 0x2
+    field public static final int NAVIGATION_STARTED = 1; // 0x1
+    field public static final int TAB_HIDDEN = 6; // 0x6
+    field public static final int TAB_SHOWN = 5; // 0x5
+  }
+
+  public class CustomTabsClient {
+    method public static boolean bindCustomTabsService(android.content.Context, String?, androidx.browser.customtabs.CustomTabsServiceConnection);
+    method public static boolean bindCustomTabsServicePreservePriority(android.content.Context, String?, androidx.browser.customtabs.CustomTabsServiceConnection);
+    method public static boolean connectAndInitialize(android.content.Context, String);
+    method public android.os.Bundle? extraCommand(String, android.os.Bundle?);
+    method public static String? getPackageName(android.content.Context, java.util.List<java.lang.String!>?);
+    method public static String? getPackageName(android.content.Context, java.util.List<java.lang.String!>?, boolean);
+    method public androidx.browser.customtabs.CustomTabsSession? newSession(androidx.browser.customtabs.CustomTabsCallback?);
+    method public androidx.browser.customtabs.CustomTabsSession? newSession(androidx.browser.customtabs.CustomTabsCallback?, int);
+    method public boolean warmup(long);
+  }
+
+  public final class CustomTabsIntent {
+    method public static int getActivityResizeBehavior(android.content.Intent);
+    method public static int getCloseButtonPosition(android.content.Intent);
+    method public static androidx.browser.customtabs.CustomTabColorSchemeParams getColorSchemeParams(android.content.Intent, int);
+    method @Dimension(unit=androidx.annotation.Dimension.PX) public static int getInitialActivityHeightPx(android.content.Intent);
+    method public static int getMaxToolbarItems();
+    method @Dimension(unit=androidx.annotation.Dimension.DP) public static int getToolbarCornerRadiusDp(android.content.Intent);
+    method public void launchUrl(android.content.Context, android.net.Uri);
+    method public static android.content.Intent setAlwaysUseBrowserUI(android.content.Intent?);
+    method public static boolean shouldAlwaysUseBrowserUI(android.content.Intent);
+    field public static final int ACTIVITY_HEIGHT_ADJUSTABLE = 1; // 0x1
+    field public static final int ACTIVITY_HEIGHT_DEFAULT = 0; // 0x0
+    field public static final int ACTIVITY_HEIGHT_FIXED = 2; // 0x2
+    field public static final int CLOSE_BUTTON_POSITION_DEFAULT = 0; // 0x0
+    field public static final int CLOSE_BUTTON_POSITION_END = 2; // 0x2
+    field public static final int CLOSE_BUTTON_POSITION_START = 1; // 0x1
+    field public static final int COLOR_SCHEME_DARK = 2; // 0x2
+    field public static final int COLOR_SCHEME_LIGHT = 1; // 0x1
+    field public static final int COLOR_SCHEME_SYSTEM = 0; // 0x0
+    field public static final String EXTRA_ACTION_BUTTON_BUNDLE = "android.support.customtabs.extra.ACTION_BUTTON_BUNDLE";
+    field public static final String EXTRA_ACTIVITY_HEIGHT_RESIZE_BEHAVIOR = "androidx.browser.customtabs.extra.ACTIVITY_HEIGHT_RESIZE_BEHAVIOR";
+    field public static final String EXTRA_CLOSE_BUTTON_ICON = "android.support.customtabs.extra.CLOSE_BUTTON_ICON";
+    field public static final String EXTRA_CLOSE_BUTTON_POSITION = "androidx.browser.customtabs.extra.CLOSE_BUTTON_POSITION";
+    field public static final String EXTRA_COLOR_SCHEME = "androidx.browser.customtabs.extra.COLOR_SCHEME";
+    field public static final String EXTRA_COLOR_SCHEME_PARAMS = "androidx.browser.customtabs.extra.COLOR_SCHEME_PARAMS";
+    field @Deprecated public static final String EXTRA_DEFAULT_SHARE_MENU_ITEM = "android.support.customtabs.extra.SHARE_MENU_ITEM";
+    field public static final String EXTRA_ENABLE_INSTANT_APPS = "android.support.customtabs.extra.EXTRA_ENABLE_INSTANT_APPS";
+    field public static final String EXTRA_ENABLE_URLBAR_HIDING = "android.support.customtabs.extra.ENABLE_URLBAR_HIDING";
+    field public static final String EXTRA_EXIT_ANIMATION_BUNDLE = "android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE";
+    field public static final String EXTRA_INITIAL_ACTIVITY_HEIGHT_PX = "androidx.browser.customtabs.extra.INITIAL_ACTIVITY_HEIGHT_PX";
+    field public static final String EXTRA_MENU_ITEMS = "android.support.customtabs.extra.MENU_ITEMS";
+    field public static final String EXTRA_NAVIGATION_BAR_COLOR = "androidx.browser.customtabs.extra.NAVIGATION_BAR_COLOR";
+    field public static final String EXTRA_NAVIGATION_BAR_DIVIDER_COLOR = "androidx.browser.customtabs.extra.NAVIGATION_BAR_DIVIDER_COLOR";
+    field public static final String EXTRA_REMOTEVIEWS = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS";
+    field public static final String EXTRA_REMOTEVIEWS_CLICKED_ID = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_CLICKED_ID";
+    field public static final String EXTRA_REMOTEVIEWS_PENDINGINTENT = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_PENDINGINTENT";
+    field public static final String EXTRA_REMOTEVIEWS_VIEW_IDS = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_VIEW_IDS";
+    field public static final String EXTRA_SECONDARY_TOOLBAR_COLOR = "android.support.customtabs.extra.SECONDARY_TOOLBAR_COLOR";
+    field public static final String EXTRA_SESSION = "android.support.customtabs.extra.SESSION";
+    field public static final String EXTRA_SHARE_STATE = "androidx.browser.customtabs.extra.SHARE_STATE";
+    field public static final String EXTRA_TINT_ACTION_BUTTON = "android.support.customtabs.extra.TINT_ACTION_BUTTON";
+    field public static final String EXTRA_TITLE_VISIBILITY_STATE = "android.support.customtabs.extra.TITLE_VISIBILITY";
+    field public static final String EXTRA_TOOLBAR_COLOR = "android.support.customtabs.extra.TOOLBAR_COLOR";
+    field public static final String EXTRA_TOOLBAR_CORNER_RADIUS_DP = "androidx.browser.customtabs.extra.TOOLBAR_CORNER_RADIUS_DP";
+    field public static final String EXTRA_TOOLBAR_ITEMS = "android.support.customtabs.extra.TOOLBAR_ITEMS";
+    field public static final String KEY_DESCRIPTION = "android.support.customtabs.customaction.DESCRIPTION";
+    field public static final String KEY_ICON = "android.support.customtabs.customaction.ICON";
+    field public static final String KEY_ID = "android.support.customtabs.customaction.ID";
+    field public static final String KEY_MENU_ITEM_TITLE = "android.support.customtabs.customaction.MENU_ITEM_TITLE";
+    field public static final String KEY_PENDING_INTENT = "android.support.customtabs.customaction.PENDING_INTENT";
+    field public static final int NO_TITLE = 0; // 0x0
+    field public static final int SHARE_STATE_DEFAULT = 0; // 0x0
+    field public static final int SHARE_STATE_OFF = 2; // 0x2
+    field public static final int SHARE_STATE_ON = 1; // 0x1
+    field public static final int SHOW_PAGE_TITLE = 1; // 0x1
+    field public static final int TOOLBAR_ACTION_BUTTON_ID = 0; // 0x0
+    field public final android.content.Intent intent;
+    field public final android.os.Bundle? startAnimationBundle;
+  }
+
+  public static final class CustomTabsIntent.Builder {
+    ctor public CustomTabsIntent.Builder();
+    ctor public CustomTabsIntent.Builder(androidx.browser.customtabs.CustomTabsSession?);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder addDefaultShareMenuItem();
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder addMenuItem(String, android.app.PendingIntent);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder addToolbarItem(int, android.graphics.Bitmap, String, android.app.PendingIntent) throws java.lang.IllegalStateException;
+    method public androidx.browser.customtabs.CustomTabsIntent build();
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder enableUrlBarHiding();
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, String, android.app.PendingIntent, boolean);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, String, android.app.PendingIntent);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setCloseButtonIcon(android.graphics.Bitmap);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setCloseButtonPosition(int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setColorScheme(int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setColorSchemeParams(int, androidx.browser.customtabs.CustomTabColorSchemeParams);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setDefaultColorSchemeParams(androidx.browser.customtabs.CustomTabColorSchemeParams);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setDefaultShareMenuItemEnabled(boolean);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setExitAnimations(android.content.Context, @AnimRes int, @AnimRes int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setInitialActivityHeightPx(@Dimension(unit=androidx.annotation.Dimension.PX) int, int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setInitialActivityHeightPx(@Dimension(unit=androidx.annotation.Dimension.PX) int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setInstantAppsEnabled(boolean);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setNavigationBarColor(@ColorInt int);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setNavigationBarDividerColor(@ColorInt int);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setSecondaryToolbarColor(@ColorInt int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setSecondaryToolbarViews(android.widget.RemoteViews, int[]?, android.app.PendingIntent?);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setSession(androidx.browser.customtabs.CustomTabsSession);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setShareState(int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setShowTitle(boolean);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setStartAnimations(android.content.Context, @AnimRes int, @AnimRes int);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setToolbarColor(@ColorInt int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setToolbarCornerRadiusDp(@Dimension(unit=androidx.annotation.Dimension.DP) int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setUrlBarHidingEnabled(boolean);
+  }
+
+  public abstract class CustomTabsService extends android.app.Service {
+    ctor public CustomTabsService();
+    method protected boolean cleanUpSession(androidx.browser.customtabs.CustomTabsSessionToken);
+    method protected abstract android.os.Bundle? extraCommand(String, android.os.Bundle?);
+    method protected abstract boolean mayLaunchUrl(androidx.browser.customtabs.CustomTabsSessionToken, android.net.Uri?, android.os.Bundle?, java.util.List<android.os.Bundle!>?);
+    method protected abstract boolean newSession(androidx.browser.customtabs.CustomTabsSessionToken);
+    method public android.os.IBinder onBind(android.content.Intent?);
+    method @androidx.browser.customtabs.CustomTabsService.Result protected abstract int postMessage(androidx.browser.customtabs.CustomTabsSessionToken, String, android.os.Bundle?);
+    method protected abstract boolean receiveFile(androidx.browser.customtabs.CustomTabsSessionToken, android.net.Uri, int, android.os.Bundle?);
+    method protected abstract boolean requestPostMessageChannel(androidx.browser.customtabs.CustomTabsSessionToken, android.net.Uri);
+    method protected abstract boolean updateVisuals(androidx.browser.customtabs.CustomTabsSessionToken, android.os.Bundle?);
+    method protected abstract boolean validateRelationship(androidx.browser.customtabs.CustomTabsSessionToken, @androidx.browser.customtabs.CustomTabsService.Relation int, android.net.Uri, android.os.Bundle?);
+    method protected abstract boolean warmup(long);
+    field public static final String ACTION_CUSTOM_TABS_CONNECTION = "android.support.customtabs.action.CustomTabsService";
+    field public static final String CATEGORY_COLOR_SCHEME_CUSTOMIZATION = "androidx.browser.customtabs.category.ColorSchemeCustomization";
+    field public static final String CATEGORY_NAVBAR_COLOR_CUSTOMIZATION = "androidx.browser.customtabs.category.NavBarColorCustomization";
+    field public static final String CATEGORY_TRUSTED_WEB_ACTIVITY_IMMERSIVE_MODE = "androidx.browser.trusted.category.ImmersiveMode";
+    field public static final String CATEGORY_WEB_SHARE_TARGET_V2 = "androidx.browser.trusted.category.WebShareTargetV2";
+    field public static final int FILE_PURPOSE_TRUSTED_WEB_ACTIVITY_SPLASH_IMAGE = 1; // 0x1
+    field public static final String KEY_SUCCESS = "androidx.browser.customtabs.SUCCESS";
+    field public static final String KEY_URL = "android.support.customtabs.otherurls.URL";
+    field public static final int RELATION_HANDLE_ALL_URLS = 2; // 0x2
+    field public static final int RELATION_USE_AS_ORIGIN = 1; // 0x1
+    field public static final int RESULT_FAILURE_DISALLOWED = -1; // 0xffffffff
+    field public static final int RESULT_FAILURE_MESSAGING_ERROR = -3; // 0xfffffffd
+    field public static final int RESULT_FAILURE_REMOTE_ERROR = -2; // 0xfffffffe
+    field public static final int RESULT_SUCCESS = 0; // 0x0
+    field public static final String TRUSTED_WEB_ACTIVITY_CATEGORY = "androidx.browser.trusted.category.TrustedWebActivities";
+  }
+
+  @IntDef({androidx.browser.customtabs.CustomTabsService.RELATION_USE_AS_ORIGIN, androidx.browser.customtabs.CustomTabsService.RELATION_HANDLE_ALL_URLS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface CustomTabsService.Relation {
+  }
+
+  @IntDef({androidx.browser.customtabs.CustomTabsService.RESULT_SUCCESS, androidx.browser.customtabs.CustomTabsService.RESULT_FAILURE_DISALLOWED, androidx.browser.customtabs.CustomTabsService.RESULT_FAILURE_REMOTE_ERROR, androidx.browser.customtabs.CustomTabsService.RESULT_FAILURE_MESSAGING_ERROR}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface CustomTabsService.Result {
+  }
+
+  public abstract class CustomTabsServiceConnection implements android.content.ServiceConnection {
+    ctor public CustomTabsServiceConnection();
+    method public abstract void onCustomTabsServiceConnected(android.content.ComponentName, androidx.browser.customtabs.CustomTabsClient);
+    method public final void onServiceConnected(android.content.ComponentName, android.os.IBinder);
+  }
+
+  public final class CustomTabsSession {
+    method @VisibleForTesting public static androidx.browser.customtabs.CustomTabsSession createMockSessionForTesting(android.content.ComponentName);
+    method public boolean mayLaunchUrl(android.net.Uri?, android.os.Bundle?, java.util.List<android.os.Bundle!>?);
+    method @androidx.browser.customtabs.CustomTabsService.Result public int postMessage(String, android.os.Bundle?);
+    method public boolean receiveFile(android.net.Uri, int, android.os.Bundle?);
+    method public boolean requestPostMessageChannel(android.net.Uri);
+    method public boolean setActionButton(android.graphics.Bitmap, String);
+    method public boolean setSecondaryToolbarViews(android.widget.RemoteViews?, int[]?, android.app.PendingIntent?);
+    method @Deprecated public boolean setToolbarItem(int, android.graphics.Bitmap, String);
+    method public boolean validateRelationship(@androidx.browser.customtabs.CustomTabsService.Relation int, android.net.Uri, android.os.Bundle?);
+  }
+
+  public class CustomTabsSessionToken {
+    method public static androidx.browser.customtabs.CustomTabsSessionToken createMockSessionTokenForTesting();
+    method public androidx.browser.customtabs.CustomTabsCallback? getCallback();
+    method public static androidx.browser.customtabs.CustomTabsSessionToken? getSessionTokenFromIntent(android.content.Intent);
+    method public boolean isAssociatedWith(androidx.browser.customtabs.CustomTabsSession);
+  }
+
+  public class PostMessageService extends android.app.Service {
+    ctor public PostMessageService();
+    method public android.os.IBinder onBind(android.content.Intent?);
+  }
+
+  public abstract class PostMessageServiceConnection implements android.content.ServiceConnection {
+    ctor public PostMessageServiceConnection(androidx.browser.customtabs.CustomTabsSessionToken);
+    method public boolean bindSessionToPostMessageService(android.content.Context, String);
+    method public final boolean notifyMessageChannelReady(android.os.Bundle?);
+    method public void onPostMessageServiceConnected();
+    method public void onPostMessageServiceDisconnected();
+    method public final void onServiceConnected(android.content.ComponentName, android.os.IBinder);
+    method public final void onServiceDisconnected(android.content.ComponentName);
+    method public final boolean postMessage(String, android.os.Bundle?);
+    method public void unbindFromContext(android.content.Context);
+  }
+
+  public class TrustedWebUtils {
+    method public static boolean areSplashScreensSupported(android.content.Context, String, String);
+    method @Deprecated public static void launchAsTrustedWebActivity(android.content.Context, androidx.browser.customtabs.CustomTabsIntent, android.net.Uri);
+    method @WorkerThread public static boolean transferSplashImage(android.content.Context, java.io.File, String, String, androidx.browser.customtabs.CustomTabsSession);
+    field public static final String EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY = "android.support.customtabs.extra.LAUNCH_AS_TRUSTED_WEB_ACTIVITY";
+  }
+
+}
+
+package androidx.browser.trusted {
+
+  public final class ScreenOrientation {
+    field public static final int ANY = 5; // 0x5
+    field public static final int DEFAULT = 0; // 0x0
+    field public static final int LANDSCAPE = 6; // 0x6
+    field public static final int LANDSCAPE_PRIMARY = 3; // 0x3
+    field public static final int LANDSCAPE_SECONDARY = 4; // 0x4
+    field public static final int NATURAL = 8; // 0x8
+    field public static final int PORTRAIT = 7; // 0x7
+    field public static final int PORTRAIT_PRIMARY = 1; // 0x1
+    field public static final int PORTRAIT_SECONDARY = 2; // 0x2
+  }
+
+  public final class Token {
+    method public static androidx.browser.trusted.Token? create(String, android.content.pm.PackageManager);
+    method public static androidx.browser.trusted.Token deserialize(byte[]);
+    method public boolean matches(String, android.content.pm.PackageManager);
+    method public byte[] serialize();
+  }
+
+  public interface TokenStore {
+    method @BinderThread public androidx.browser.trusted.Token? load();
+    method @WorkerThread public void store(androidx.browser.trusted.Token?);
+  }
+
+  public abstract class TrustedWebActivityCallback {
+    ctor public TrustedWebActivityCallback();
+    method public abstract void onExtraCallback(String, android.os.Bundle?);
+  }
+
+  public class TrustedWebActivityCallbackRemote {
+    method public void runExtraCallback(String, android.os.Bundle) throws android.os.RemoteException;
+  }
+
+  public interface TrustedWebActivityDisplayMode {
+    method public static androidx.browser.trusted.TrustedWebActivityDisplayMode fromBundle(android.os.Bundle);
+    method public android.os.Bundle toBundle();
+    field public static final String KEY_ID = "androidx.browser.trusted.displaymode.KEY_ID";
+  }
+
+  public static class TrustedWebActivityDisplayMode.DefaultMode implements androidx.browser.trusted.TrustedWebActivityDisplayMode {
+    ctor public TrustedWebActivityDisplayMode.DefaultMode();
+    method public android.os.Bundle toBundle();
+  }
+
+  public static class TrustedWebActivityDisplayMode.ImmersiveMode implements androidx.browser.trusted.TrustedWebActivityDisplayMode {
+    ctor public TrustedWebActivityDisplayMode.ImmersiveMode(boolean, int);
+    method public boolean isSticky();
+    method public int layoutInDisplayCutoutMode();
+    method public android.os.Bundle toBundle();
+    field public static final String KEY_CUTOUT_MODE = "androidx.browser.trusted.displaymode.KEY_CUTOUT_MODE";
+    field public static final String KEY_STICKY = "androidx.browser.trusted.displaymode.KEY_STICKY";
+  }
+
+  public final class TrustedWebActivityIntent {
+    method public android.content.Intent getIntent();
+    method public void launchTrustedWebActivity(android.content.Context);
+  }
+
+  public class TrustedWebActivityIntentBuilder {
+    ctor public TrustedWebActivityIntentBuilder(android.net.Uri);
+    method public androidx.browser.trusted.TrustedWebActivityIntent build(androidx.browser.customtabs.CustomTabsSession);
+    method public androidx.browser.customtabs.CustomTabsIntent buildCustomTabsIntent();
+    method public androidx.browser.trusted.TrustedWebActivityDisplayMode getDisplayMode();
+    method public android.net.Uri getUri();
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setAdditionalTrustedOrigins(java.util.List<java.lang.String!>);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setColorScheme(int);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setColorSchemeParams(int, androidx.browser.customtabs.CustomTabColorSchemeParams);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setDefaultColorSchemeParams(androidx.browser.customtabs.CustomTabColorSchemeParams);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setDisplayMode(androidx.browser.trusted.TrustedWebActivityDisplayMode);
+    method @Deprecated public androidx.browser.trusted.TrustedWebActivityIntentBuilder setNavigationBarColor(@ColorInt int);
+    method @Deprecated public androidx.browser.trusted.TrustedWebActivityIntentBuilder setNavigationBarDividerColor(@ColorInt int);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setScreenOrientation(int);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setShareParams(androidx.browser.trusted.sharing.ShareTarget, androidx.browser.trusted.sharing.ShareData);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setSplashScreenParams(android.os.Bundle);
+    method @Deprecated public androidx.browser.trusted.TrustedWebActivityIntentBuilder setToolbarColor(@ColorInt int);
+    field public static final String EXTRA_ADDITIONAL_TRUSTED_ORIGINS = "android.support.customtabs.extra.ADDITIONAL_TRUSTED_ORIGINS";
+    field public static final String EXTRA_DISPLAY_MODE = "androidx.browser.trusted.extra.DISPLAY_MODE";
+    field public static final String EXTRA_SCREEN_ORIENTATION = "androidx.browser.trusted.extra.SCREEN_ORIENTATION";
+    field public static final String EXTRA_SHARE_DATA = "androidx.browser.trusted.extra.SHARE_DATA";
+    field public static final String EXTRA_SHARE_TARGET = "androidx.browser.trusted.extra.SHARE_TARGET";
+    field public static final String EXTRA_SPLASH_SCREEN_PARAMS = "androidx.browser.trusted.EXTRA_SPLASH_SCREEN_PARAMS";
+  }
+
+  public abstract class TrustedWebActivityService extends android.app.Service {
+    ctor public TrustedWebActivityService();
+    method @BinderThread public abstract androidx.browser.trusted.TokenStore getTokenStore();
+    method @BinderThread public boolean onAreNotificationsEnabled(String);
+    method @MainThread public final android.os.IBinder? onBind(android.content.Intent?);
+    method @BinderThread public void onCancelNotification(String, int);
+    method @BinderThread public android.os.Bundle? onExtraCommand(String, android.os.Bundle, androidx.browser.trusted.TrustedWebActivityCallbackRemote?);
+    method @BinderThread public android.os.Bundle onGetSmallIconBitmap();
+    method @BinderThread public int onGetSmallIconId();
+    method @BinderThread @RequiresPermission(android.Manifest.permission.POST_NOTIFICATIONS) public boolean onNotifyNotificationWithChannel(String, int, android.app.Notification, String);
+    method @MainThread public final boolean onUnbind(android.content.Intent?);
+    field public static final String ACTION_TRUSTED_WEB_ACTIVITY_SERVICE = "android.support.customtabs.trusted.TRUSTED_WEB_ACTIVITY_SERVICE";
+    field public static final String KEY_SMALL_ICON_BITMAP = "android.support.customtabs.trusted.SMALL_ICON_BITMAP";
+    field public static final String KEY_SUCCESS = "androidx.browser.trusted.SUCCESS";
+    field public static final String META_DATA_NAME_SMALL_ICON = "android.support.customtabs.trusted.SMALL_ICON";
+    field public static final int SMALL_ICON_NOT_SET = -1; // 0xffffffff
+  }
+
+  public final class TrustedWebActivityServiceConnection {
+    method public boolean areNotificationsEnabled(String) throws android.os.RemoteException;
+    method public void cancel(String, int) throws android.os.RemoteException;
+    method public android.content.ComponentName getComponentName();
+    method public android.graphics.Bitmap? getSmallIconBitmap() throws android.os.RemoteException;
+    method public int getSmallIconId() throws android.os.RemoteException;
+    method public boolean notify(String, int, android.app.Notification, String) throws android.os.RemoteException;
+    method public android.os.Bundle? sendExtraCommand(String, android.os.Bundle, androidx.browser.trusted.TrustedWebActivityCallback?) throws android.os.RemoteException;
+  }
+
+  public final class TrustedWebActivityServiceConnectionPool {
+    method @MainThread public com.google.common.util.concurrent.ListenableFuture<androidx.browser.trusted.TrustedWebActivityServiceConnection!> connect(android.net.Uri, java.util.Set<androidx.browser.trusted.Token!>, java.util.concurrent.Executor);
+    method public static androidx.browser.trusted.TrustedWebActivityServiceConnectionPool create(android.content.Context);
+    method @MainThread public boolean serviceExistsForScope(android.net.Uri, java.util.Set<androidx.browser.trusted.Token!>);
+  }
+
+}
+
+package androidx.browser.trusted.sharing {
+
+  public final class ShareData {
+    ctor public ShareData(String?, String?, java.util.List<android.net.Uri!>?);
+    method public static androidx.browser.trusted.sharing.ShareData fromBundle(android.os.Bundle);
+    method public android.os.Bundle toBundle();
+    field public static final String KEY_TEXT = "androidx.browser.trusted.sharing.KEY_TEXT";
+    field public static final String KEY_TITLE = "androidx.browser.trusted.sharing.KEY_TITLE";
+    field public static final String KEY_URIS = "androidx.browser.trusted.sharing.KEY_URIS";
+    field public final String? text;
+    field public final String? title;
+    field public final java.util.List<android.net.Uri!>? uris;
+  }
+
+  public final class ShareTarget {
+    ctor public ShareTarget(String, String?, String?, androidx.browser.trusted.sharing.ShareTarget.Params);
+    method public static androidx.browser.trusted.sharing.ShareTarget? fromBundle(android.os.Bundle);
+    method public android.os.Bundle toBundle();
+    field public static final String ENCODING_TYPE_MULTIPART = "multipart/form-data";
+    field public static final String ENCODING_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
+    field public static final String KEY_ACTION = "androidx.browser.trusted.sharing.KEY_ACTION";
+    field public static final String KEY_ENCTYPE = "androidx.browser.trusted.sharing.KEY_ENCTYPE";
+    field public static final String KEY_METHOD = "androidx.browser.trusted.sharing.KEY_METHOD";
+    field public static final String KEY_PARAMS = "androidx.browser.trusted.sharing.KEY_PARAMS";
+    field public static final String METHOD_GET = "GET";
+    field public static final String METHOD_POST = "POST";
+    field public final String action;
+    field public final String? encodingType;
+    field public final String? method;
+    field public final androidx.browser.trusted.sharing.ShareTarget.Params params;
+  }
+
+  public static final class ShareTarget.FileFormField {
+    ctor public ShareTarget.FileFormField(String, java.util.List<java.lang.String!>);
+    field public static final String KEY_ACCEPTED_TYPES = "androidx.browser.trusted.sharing.KEY_ACCEPTED_TYPES";
+    field public static final String KEY_NAME = "androidx.browser.trusted.sharing.KEY_FILE_NAME";
+    field public final java.util.List<java.lang.String!> acceptedTypes;
+    field public final String name;
+  }
+
+  public static class ShareTarget.Params {
+    ctor public ShareTarget.Params(String?, String?, java.util.List<androidx.browser.trusted.sharing.ShareTarget.FileFormField!>?);
+    field public static final String KEY_FILES = "androidx.browser.trusted.sharing.KEY_FILES";
+    field public static final String KEY_TEXT = "androidx.browser.trusted.sharing.KEY_TEXT";
+    field public static final String KEY_TITLE = "androidx.browser.trusted.sharing.KEY_TITLE";
+    field public final java.util.List<androidx.browser.trusted.sharing.ShareTarget.FileFormField!>? files;
+    field public final String? text;
+    field public final String? title;
+  }
+
+}
+
+package androidx.browser.trusted.splashscreens {
+
+  public final class SplashScreenParamKey {
+    field public static final String KEY_BACKGROUND_COLOR = "androidx.browser.trusted.trusted.KEY_SPLASH_SCREEN_BACKGROUND_COLOR";
+    field public static final String KEY_FADE_OUT_DURATION_MS = "androidx.browser.trusted.KEY_SPLASH_SCREEN_FADE_OUT_DURATION";
+    field public static final String KEY_IMAGE_TRANSFORMATION_MATRIX = "androidx.browser.trusted.KEY_SPLASH_SCREEN_TRANSFORMATION_MATRIX";
+    field public static final String KEY_SCALE_TYPE = "androidx.browser.trusted.KEY_SPLASH_SCREEN_SCALE_TYPE";
+    field public static final String KEY_VERSION = "androidx.browser.trusted.KEY_SPLASH_SCREEN_VERSION";
+  }
+
+  public final class SplashScreenVersion {
+    field public static final String V1 = "androidx.browser.trusted.category.TrustedWebActivitySplashScreensV1";
+  }
+
+}
+
diff --git a/browser/browser/api/public_plus_experimental_1.5.0-beta02.txt b/browser/browser/api/public_plus_experimental_1.5.0-beta02.txt
new file mode 100644
index 0000000..0598270
--- /dev/null
+++ b/browser/browser/api/public_plus_experimental_1.5.0-beta02.txt
@@ -0,0 +1,469 @@
+// Signature format: 4.0
+package androidx.browser.browseractions {
+
+  @Deprecated public class BrowserActionItem {
+    ctor @Deprecated public BrowserActionItem(String, android.app.PendingIntent, @DrawableRes int);
+    ctor @Deprecated public BrowserActionItem(String, android.app.PendingIntent);
+    method @Deprecated public android.app.PendingIntent getAction();
+    method @Deprecated public int getIconId();
+    method @Deprecated public String getTitle();
+  }
+
+  @Deprecated public class BrowserActionsIntent {
+    method @Deprecated public static String? getCreatorPackageName(android.content.Intent);
+    method @Deprecated public android.content.Intent getIntent();
+    method @Deprecated public static String? getUntrustedCreatorPackageName(android.content.Intent);
+    method @Deprecated public static void launchIntent(android.content.Context, android.content.Intent);
+    method @Deprecated public static void openBrowserAction(android.content.Context, android.net.Uri);
+    method @Deprecated public static void openBrowserAction(android.content.Context, android.net.Uri, int, java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem!>, android.app.PendingIntent);
+    method @Deprecated public static java.util.List<androidx.browser.browseractions.BrowserActionItem!> parseBrowserActionItems(java.util.ArrayList<android.os.Bundle!>);
+    field @Deprecated public static final String ACTION_BROWSER_ACTIONS_OPEN = "androidx.browser.browseractions.browser_action_open";
+    field @Deprecated public static final String EXTRA_APP_ID = "androidx.browser.browseractions.APP_ID";
+    field @Deprecated public static final String EXTRA_MENU_ITEMS = "androidx.browser.browseractions.extra.MENU_ITEMS";
+    field @Deprecated public static final String EXTRA_SELECTED_ACTION_PENDING_INTENT = "androidx.browser.browseractions.extra.SELECTED_ACTION_PENDING_INTENT";
+    field @Deprecated public static final String EXTRA_TYPE = "androidx.browser.browseractions.extra.TYPE";
+    field @Deprecated public static final int ITEM_COPY = 3; // 0x3
+    field @Deprecated public static final int ITEM_DOWNLOAD = 2; // 0x2
+    field @Deprecated public static final int ITEM_INVALID_ITEM = -1; // 0xffffffff
+    field @Deprecated public static final int ITEM_OPEN_IN_INCOGNITO = 1; // 0x1
+    field @Deprecated public static final int ITEM_OPEN_IN_NEW_TAB = 0; // 0x0
+    field @Deprecated public static final int ITEM_SHARE = 4; // 0x4
+    field @Deprecated public static final String KEY_ACTION = "androidx.browser.browseractions.ACTION";
+    field @Deprecated public static final String KEY_ICON_ID = "androidx.browser.browseractions.ICON_ID";
+    field @Deprecated public static final String KEY_TITLE = "androidx.browser.browseractions.TITLE";
+    field @Deprecated public static final int MAX_CUSTOM_ITEMS = 5; // 0x5
+    field @Deprecated public static final int URL_TYPE_AUDIO = 3; // 0x3
+    field @Deprecated public static final int URL_TYPE_FILE = 4; // 0x4
+    field @Deprecated public static final int URL_TYPE_IMAGE = 1; // 0x1
+    field @Deprecated public static final int URL_TYPE_NONE = 0; // 0x0
+    field @Deprecated public static final int URL_TYPE_PLUGIN = 5; // 0x5
+    field @Deprecated public static final int URL_TYPE_VIDEO = 2; // 0x2
+  }
+
+  @Deprecated public static final class BrowserActionsIntent.Builder {
+    ctor @Deprecated public BrowserActionsIntent.Builder(android.content.Context, android.net.Uri);
+    method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent build();
+    method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder setCustomItems(java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem!>);
+    method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder setCustomItems(androidx.browser.browseractions.BrowserActionItem!...);
+    method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder setOnItemSelectedAction(android.app.PendingIntent);
+    method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder setUrlType(int);
+  }
+
+}
+
+package androidx.browser.customtabs {
+
+  public final class CustomTabColorSchemeParams {
+    field @ColorInt public final Integer? navigationBarColor;
+    field @ColorInt public final Integer? navigationBarDividerColor;
+    field @ColorInt public final Integer? secondaryToolbarColor;
+    field @ColorInt public final Integer? toolbarColor;
+  }
+
+  public static final class CustomTabColorSchemeParams.Builder {
+    ctor public CustomTabColorSchemeParams.Builder();
+    method public androidx.browser.customtabs.CustomTabColorSchemeParams build();
+    method public androidx.browser.customtabs.CustomTabColorSchemeParams.Builder setNavigationBarColor(@ColorInt int);
+    method public androidx.browser.customtabs.CustomTabColorSchemeParams.Builder setNavigationBarDividerColor(@ColorInt int);
+    method public androidx.browser.customtabs.CustomTabColorSchemeParams.Builder setSecondaryToolbarColor(@ColorInt int);
+    method public androidx.browser.customtabs.CustomTabColorSchemeParams.Builder setToolbarColor(@ColorInt int);
+  }
+
+  public class CustomTabsCallback {
+    ctor public CustomTabsCallback();
+    method public void extraCallback(String, android.os.Bundle?);
+    method public android.os.Bundle? extraCallbackWithResult(String, android.os.Bundle?);
+    method public void onActivityResized(@Dimension(unit=androidx.annotation.Dimension.PX) int, @Dimension(unit=androidx.annotation.Dimension.PX) int, android.os.Bundle);
+    method public void onMessageChannelReady(android.os.Bundle?);
+    method public void onNavigationEvent(int, android.os.Bundle?);
+    method public void onPostMessage(String, android.os.Bundle?);
+    method public void onRelationshipValidationResult(@androidx.browser.customtabs.CustomTabsService.Relation int, android.net.Uri, boolean, android.os.Bundle?);
+    field public static final int NAVIGATION_ABORTED = 4; // 0x4
+    field public static final int NAVIGATION_FAILED = 3; // 0x3
+    field public static final int NAVIGATION_FINISHED = 2; // 0x2
+    field public static final int NAVIGATION_STARTED = 1; // 0x1
+    field public static final int TAB_HIDDEN = 6; // 0x6
+    field public static final int TAB_SHOWN = 5; // 0x5
+  }
+
+  public class CustomTabsClient {
+    method public static boolean bindCustomTabsService(android.content.Context, String?, androidx.browser.customtabs.CustomTabsServiceConnection);
+    method public static boolean bindCustomTabsServicePreservePriority(android.content.Context, String?, androidx.browser.customtabs.CustomTabsServiceConnection);
+    method public static boolean connectAndInitialize(android.content.Context, String);
+    method public android.os.Bundle? extraCommand(String, android.os.Bundle?);
+    method public static String? getPackageName(android.content.Context, java.util.List<java.lang.String!>?);
+    method public static String? getPackageName(android.content.Context, java.util.List<java.lang.String!>?, boolean);
+    method public androidx.browser.customtabs.CustomTabsSession? newSession(androidx.browser.customtabs.CustomTabsCallback?);
+    method public androidx.browser.customtabs.CustomTabsSession? newSession(androidx.browser.customtabs.CustomTabsCallback?, int);
+    method public boolean warmup(long);
+  }
+
+  public final class CustomTabsIntent {
+    method public static int getActivityResizeBehavior(android.content.Intent);
+    method public static int getCloseButtonPosition(android.content.Intent);
+    method public static androidx.browser.customtabs.CustomTabColorSchemeParams getColorSchemeParams(android.content.Intent, int);
+    method @Dimension(unit=androidx.annotation.Dimension.PX) public static int getInitialActivityHeightPx(android.content.Intent);
+    method public static int getMaxToolbarItems();
+    method @Dimension(unit=androidx.annotation.Dimension.DP) public static int getToolbarCornerRadiusDp(android.content.Intent);
+    method public void launchUrl(android.content.Context, android.net.Uri);
+    method public static android.content.Intent setAlwaysUseBrowserUI(android.content.Intent?);
+    method public static boolean shouldAlwaysUseBrowserUI(android.content.Intent);
+    field public static final int ACTIVITY_HEIGHT_ADJUSTABLE = 1; // 0x1
+    field public static final int ACTIVITY_HEIGHT_DEFAULT = 0; // 0x0
+    field public static final int ACTIVITY_HEIGHT_FIXED = 2; // 0x2
+    field public static final int CLOSE_BUTTON_POSITION_DEFAULT = 0; // 0x0
+    field public static final int CLOSE_BUTTON_POSITION_END = 2; // 0x2
+    field public static final int CLOSE_BUTTON_POSITION_START = 1; // 0x1
+    field public static final int COLOR_SCHEME_DARK = 2; // 0x2
+    field public static final int COLOR_SCHEME_LIGHT = 1; // 0x1
+    field public static final int COLOR_SCHEME_SYSTEM = 0; // 0x0
+    field public static final String EXTRA_ACTION_BUTTON_BUNDLE = "android.support.customtabs.extra.ACTION_BUTTON_BUNDLE";
+    field public static final String EXTRA_ACTIVITY_HEIGHT_RESIZE_BEHAVIOR = "androidx.browser.customtabs.extra.ACTIVITY_HEIGHT_RESIZE_BEHAVIOR";
+    field public static final String EXTRA_CLOSE_BUTTON_ICON = "android.support.customtabs.extra.CLOSE_BUTTON_ICON";
+    field public static final String EXTRA_CLOSE_BUTTON_POSITION = "androidx.browser.customtabs.extra.CLOSE_BUTTON_POSITION";
+    field public static final String EXTRA_COLOR_SCHEME = "androidx.browser.customtabs.extra.COLOR_SCHEME";
+    field public static final String EXTRA_COLOR_SCHEME_PARAMS = "androidx.browser.customtabs.extra.COLOR_SCHEME_PARAMS";
+    field @Deprecated public static final String EXTRA_DEFAULT_SHARE_MENU_ITEM = "android.support.customtabs.extra.SHARE_MENU_ITEM";
+    field public static final String EXTRA_ENABLE_INSTANT_APPS = "android.support.customtabs.extra.EXTRA_ENABLE_INSTANT_APPS";
+    field public static final String EXTRA_ENABLE_URLBAR_HIDING = "android.support.customtabs.extra.ENABLE_URLBAR_HIDING";
+    field public static final String EXTRA_EXIT_ANIMATION_BUNDLE = "android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE";
+    field public static final String EXTRA_INITIAL_ACTIVITY_HEIGHT_PX = "androidx.browser.customtabs.extra.INITIAL_ACTIVITY_HEIGHT_PX";
+    field public static final String EXTRA_MENU_ITEMS = "android.support.customtabs.extra.MENU_ITEMS";
+    field public static final String EXTRA_NAVIGATION_BAR_COLOR = "androidx.browser.customtabs.extra.NAVIGATION_BAR_COLOR";
+    field public static final String EXTRA_NAVIGATION_BAR_DIVIDER_COLOR = "androidx.browser.customtabs.extra.NAVIGATION_BAR_DIVIDER_COLOR";
+    field public static final String EXTRA_REMOTEVIEWS = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS";
+    field public static final String EXTRA_REMOTEVIEWS_CLICKED_ID = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_CLICKED_ID";
+    field public static final String EXTRA_REMOTEVIEWS_PENDINGINTENT = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_PENDINGINTENT";
+    field public static final String EXTRA_REMOTEVIEWS_VIEW_IDS = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_VIEW_IDS";
+    field public static final String EXTRA_SECONDARY_TOOLBAR_COLOR = "android.support.customtabs.extra.SECONDARY_TOOLBAR_COLOR";
+    field public static final String EXTRA_SESSION = "android.support.customtabs.extra.SESSION";
+    field public static final String EXTRA_SHARE_STATE = "androidx.browser.customtabs.extra.SHARE_STATE";
+    field public static final String EXTRA_TINT_ACTION_BUTTON = "android.support.customtabs.extra.TINT_ACTION_BUTTON";
+    field public static final String EXTRA_TITLE_VISIBILITY_STATE = "android.support.customtabs.extra.TITLE_VISIBILITY";
+    field public static final String EXTRA_TOOLBAR_COLOR = "android.support.customtabs.extra.TOOLBAR_COLOR";
+    field public static final String EXTRA_TOOLBAR_CORNER_RADIUS_DP = "androidx.browser.customtabs.extra.TOOLBAR_CORNER_RADIUS_DP";
+    field public static final String EXTRA_TOOLBAR_ITEMS = "android.support.customtabs.extra.TOOLBAR_ITEMS";
+    field public static final String KEY_DESCRIPTION = "android.support.customtabs.customaction.DESCRIPTION";
+    field public static final String KEY_ICON = "android.support.customtabs.customaction.ICON";
+    field public static final String KEY_ID = "android.support.customtabs.customaction.ID";
+    field public static final String KEY_MENU_ITEM_TITLE = "android.support.customtabs.customaction.MENU_ITEM_TITLE";
+    field public static final String KEY_PENDING_INTENT = "android.support.customtabs.customaction.PENDING_INTENT";
+    field public static final int NO_TITLE = 0; // 0x0
+    field public static final int SHARE_STATE_DEFAULT = 0; // 0x0
+    field public static final int SHARE_STATE_OFF = 2; // 0x2
+    field public static final int SHARE_STATE_ON = 1; // 0x1
+    field public static final int SHOW_PAGE_TITLE = 1; // 0x1
+    field public static final int TOOLBAR_ACTION_BUTTON_ID = 0; // 0x0
+    field public final android.content.Intent intent;
+    field public final android.os.Bundle? startAnimationBundle;
+  }
+
+  public static final class CustomTabsIntent.Builder {
+    ctor public CustomTabsIntent.Builder();
+    ctor public CustomTabsIntent.Builder(androidx.browser.customtabs.CustomTabsSession?);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder addDefaultShareMenuItem();
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder addMenuItem(String, android.app.PendingIntent);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder addToolbarItem(int, android.graphics.Bitmap, String, android.app.PendingIntent) throws java.lang.IllegalStateException;
+    method public androidx.browser.customtabs.CustomTabsIntent build();
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder enableUrlBarHiding();
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, String, android.app.PendingIntent, boolean);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, String, android.app.PendingIntent);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setCloseButtonIcon(android.graphics.Bitmap);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setCloseButtonPosition(int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setColorScheme(int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setColorSchemeParams(int, androidx.browser.customtabs.CustomTabColorSchemeParams);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setDefaultColorSchemeParams(androidx.browser.customtabs.CustomTabColorSchemeParams);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setDefaultShareMenuItemEnabled(boolean);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setExitAnimations(android.content.Context, @AnimRes int, @AnimRes int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setInitialActivityHeightPx(@Dimension(unit=androidx.annotation.Dimension.PX) int, int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setInitialActivityHeightPx(@Dimension(unit=androidx.annotation.Dimension.PX) int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setInstantAppsEnabled(boolean);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setNavigationBarColor(@ColorInt int);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setNavigationBarDividerColor(@ColorInt int);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setSecondaryToolbarColor(@ColorInt int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setSecondaryToolbarViews(android.widget.RemoteViews, int[]?, android.app.PendingIntent?);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setSession(androidx.browser.customtabs.CustomTabsSession);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setShareState(int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setShowTitle(boolean);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setStartAnimations(android.content.Context, @AnimRes int, @AnimRes int);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setToolbarColor(@ColorInt int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setToolbarCornerRadiusDp(@Dimension(unit=androidx.annotation.Dimension.DP) int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setUrlBarHidingEnabled(boolean);
+  }
+
+  public abstract class CustomTabsService extends android.app.Service {
+    ctor public CustomTabsService();
+    method protected boolean cleanUpSession(androidx.browser.customtabs.CustomTabsSessionToken);
+    method protected abstract android.os.Bundle? extraCommand(String, android.os.Bundle?);
+    method protected abstract boolean mayLaunchUrl(androidx.browser.customtabs.CustomTabsSessionToken, android.net.Uri?, android.os.Bundle?, java.util.List<android.os.Bundle!>?);
+    method protected abstract boolean newSession(androidx.browser.customtabs.CustomTabsSessionToken);
+    method public android.os.IBinder onBind(android.content.Intent?);
+    method @androidx.browser.customtabs.CustomTabsService.Result protected abstract int postMessage(androidx.browser.customtabs.CustomTabsSessionToken, String, android.os.Bundle?);
+    method protected abstract boolean receiveFile(androidx.browser.customtabs.CustomTabsSessionToken, android.net.Uri, int, android.os.Bundle?);
+    method protected abstract boolean requestPostMessageChannel(androidx.browser.customtabs.CustomTabsSessionToken, android.net.Uri);
+    method protected abstract boolean updateVisuals(androidx.browser.customtabs.CustomTabsSessionToken, android.os.Bundle?);
+    method protected abstract boolean validateRelationship(androidx.browser.customtabs.CustomTabsSessionToken, @androidx.browser.customtabs.CustomTabsService.Relation int, android.net.Uri, android.os.Bundle?);
+    method protected abstract boolean warmup(long);
+    field public static final String ACTION_CUSTOM_TABS_CONNECTION = "android.support.customtabs.action.CustomTabsService";
+    field public static final String CATEGORY_COLOR_SCHEME_CUSTOMIZATION = "androidx.browser.customtabs.category.ColorSchemeCustomization";
+    field public static final String CATEGORY_NAVBAR_COLOR_CUSTOMIZATION = "androidx.browser.customtabs.category.NavBarColorCustomization";
+    field public static final String CATEGORY_TRUSTED_WEB_ACTIVITY_IMMERSIVE_MODE = "androidx.browser.trusted.category.ImmersiveMode";
+    field public static final String CATEGORY_WEB_SHARE_TARGET_V2 = "androidx.browser.trusted.category.WebShareTargetV2";
+    field public static final int FILE_PURPOSE_TRUSTED_WEB_ACTIVITY_SPLASH_IMAGE = 1; // 0x1
+    field public static final String KEY_SUCCESS = "androidx.browser.customtabs.SUCCESS";
+    field public static final String KEY_URL = "android.support.customtabs.otherurls.URL";
+    field public static final int RELATION_HANDLE_ALL_URLS = 2; // 0x2
+    field public static final int RELATION_USE_AS_ORIGIN = 1; // 0x1
+    field public static final int RESULT_FAILURE_DISALLOWED = -1; // 0xffffffff
+    field public static final int RESULT_FAILURE_MESSAGING_ERROR = -3; // 0xfffffffd
+    field public static final int RESULT_FAILURE_REMOTE_ERROR = -2; // 0xfffffffe
+    field public static final int RESULT_SUCCESS = 0; // 0x0
+    field public static final String TRUSTED_WEB_ACTIVITY_CATEGORY = "androidx.browser.trusted.category.TrustedWebActivities";
+  }
+
+  @IntDef({androidx.browser.customtabs.CustomTabsService.RELATION_USE_AS_ORIGIN, androidx.browser.customtabs.CustomTabsService.RELATION_HANDLE_ALL_URLS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface CustomTabsService.Relation {
+  }
+
+  @IntDef({androidx.browser.customtabs.CustomTabsService.RESULT_SUCCESS, androidx.browser.customtabs.CustomTabsService.RESULT_FAILURE_DISALLOWED, androidx.browser.customtabs.CustomTabsService.RESULT_FAILURE_REMOTE_ERROR, androidx.browser.customtabs.CustomTabsService.RESULT_FAILURE_MESSAGING_ERROR}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface CustomTabsService.Result {
+  }
+
+  public abstract class CustomTabsServiceConnection implements android.content.ServiceConnection {
+    ctor public CustomTabsServiceConnection();
+    method public abstract void onCustomTabsServiceConnected(android.content.ComponentName, androidx.browser.customtabs.CustomTabsClient);
+    method public final void onServiceConnected(android.content.ComponentName, android.os.IBinder);
+  }
+
+  public final class CustomTabsSession {
+    method @VisibleForTesting public static androidx.browser.customtabs.CustomTabsSession createMockSessionForTesting(android.content.ComponentName);
+    method public boolean mayLaunchUrl(android.net.Uri?, android.os.Bundle?, java.util.List<android.os.Bundle!>?);
+    method @androidx.browser.customtabs.CustomTabsService.Result public int postMessage(String, android.os.Bundle?);
+    method public boolean receiveFile(android.net.Uri, int, android.os.Bundle?);
+    method public boolean requestPostMessageChannel(android.net.Uri);
+    method public boolean setActionButton(android.graphics.Bitmap, String);
+    method public boolean setSecondaryToolbarViews(android.widget.RemoteViews?, int[]?, android.app.PendingIntent?);
+    method @Deprecated public boolean setToolbarItem(int, android.graphics.Bitmap, String);
+    method public boolean validateRelationship(@androidx.browser.customtabs.CustomTabsService.Relation int, android.net.Uri, android.os.Bundle?);
+  }
+
+  public class CustomTabsSessionToken {
+    method public static androidx.browser.customtabs.CustomTabsSessionToken createMockSessionTokenForTesting();
+    method public androidx.browser.customtabs.CustomTabsCallback? getCallback();
+    method public static androidx.browser.customtabs.CustomTabsSessionToken? getSessionTokenFromIntent(android.content.Intent);
+    method public boolean isAssociatedWith(androidx.browser.customtabs.CustomTabsSession);
+  }
+
+  public class PostMessageService extends android.app.Service {
+    ctor public PostMessageService();
+    method public android.os.IBinder onBind(android.content.Intent?);
+  }
+
+  public abstract class PostMessageServiceConnection implements android.content.ServiceConnection {
+    ctor public PostMessageServiceConnection(androidx.browser.customtabs.CustomTabsSessionToken);
+    method public boolean bindSessionToPostMessageService(android.content.Context, String);
+    method public final boolean notifyMessageChannelReady(android.os.Bundle?);
+    method public void onPostMessageServiceConnected();
+    method public void onPostMessageServiceDisconnected();
+    method public final void onServiceConnected(android.content.ComponentName, android.os.IBinder);
+    method public final void onServiceDisconnected(android.content.ComponentName);
+    method public final boolean postMessage(String, android.os.Bundle?);
+    method public void unbindFromContext(android.content.Context);
+  }
+
+  public class TrustedWebUtils {
+    method public static boolean areSplashScreensSupported(android.content.Context, String, String);
+    method @Deprecated public static void launchAsTrustedWebActivity(android.content.Context, androidx.browser.customtabs.CustomTabsIntent, android.net.Uri);
+    method @WorkerThread public static boolean transferSplashImage(android.content.Context, java.io.File, String, String, androidx.browser.customtabs.CustomTabsSession);
+    field public static final String EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY = "android.support.customtabs.extra.LAUNCH_AS_TRUSTED_WEB_ACTIVITY";
+  }
+
+}
+
+package androidx.browser.trusted {
+
+  public final class ScreenOrientation {
+    field public static final int ANY = 5; // 0x5
+    field public static final int DEFAULT = 0; // 0x0
+    field public static final int LANDSCAPE = 6; // 0x6
+    field public static final int LANDSCAPE_PRIMARY = 3; // 0x3
+    field public static final int LANDSCAPE_SECONDARY = 4; // 0x4
+    field public static final int NATURAL = 8; // 0x8
+    field public static final int PORTRAIT = 7; // 0x7
+    field public static final int PORTRAIT_PRIMARY = 1; // 0x1
+    field public static final int PORTRAIT_SECONDARY = 2; // 0x2
+  }
+
+  public final class Token {
+    method public static androidx.browser.trusted.Token? create(String, android.content.pm.PackageManager);
+    method public static androidx.browser.trusted.Token deserialize(byte[]);
+    method public boolean matches(String, android.content.pm.PackageManager);
+    method public byte[] serialize();
+  }
+
+  public interface TokenStore {
+    method @BinderThread public androidx.browser.trusted.Token? load();
+    method @WorkerThread public void store(androidx.browser.trusted.Token?);
+  }
+
+  public abstract class TrustedWebActivityCallback {
+    ctor public TrustedWebActivityCallback();
+    method public abstract void onExtraCallback(String, android.os.Bundle?);
+  }
+
+  public class TrustedWebActivityCallbackRemote {
+    method public void runExtraCallback(String, android.os.Bundle) throws android.os.RemoteException;
+  }
+
+  public interface TrustedWebActivityDisplayMode {
+    method public static androidx.browser.trusted.TrustedWebActivityDisplayMode fromBundle(android.os.Bundle);
+    method public android.os.Bundle toBundle();
+    field public static final String KEY_ID = "androidx.browser.trusted.displaymode.KEY_ID";
+  }
+
+  public static class TrustedWebActivityDisplayMode.DefaultMode implements androidx.browser.trusted.TrustedWebActivityDisplayMode {
+    ctor public TrustedWebActivityDisplayMode.DefaultMode();
+    method public android.os.Bundle toBundle();
+  }
+
+  public static class TrustedWebActivityDisplayMode.ImmersiveMode implements androidx.browser.trusted.TrustedWebActivityDisplayMode {
+    ctor public TrustedWebActivityDisplayMode.ImmersiveMode(boolean, int);
+    method public boolean isSticky();
+    method public int layoutInDisplayCutoutMode();
+    method public android.os.Bundle toBundle();
+    field public static final String KEY_CUTOUT_MODE = "androidx.browser.trusted.displaymode.KEY_CUTOUT_MODE";
+    field public static final String KEY_STICKY = "androidx.browser.trusted.displaymode.KEY_STICKY";
+  }
+
+  public final class TrustedWebActivityIntent {
+    method public android.content.Intent getIntent();
+    method public void launchTrustedWebActivity(android.content.Context);
+  }
+
+  public class TrustedWebActivityIntentBuilder {
+    ctor public TrustedWebActivityIntentBuilder(android.net.Uri);
+    method public androidx.browser.trusted.TrustedWebActivityIntent build(androidx.browser.customtabs.CustomTabsSession);
+    method public androidx.browser.customtabs.CustomTabsIntent buildCustomTabsIntent();
+    method public androidx.browser.trusted.TrustedWebActivityDisplayMode getDisplayMode();
+    method public android.net.Uri getUri();
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setAdditionalTrustedOrigins(java.util.List<java.lang.String!>);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setColorScheme(int);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setColorSchemeParams(int, androidx.browser.customtabs.CustomTabColorSchemeParams);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setDefaultColorSchemeParams(androidx.browser.customtabs.CustomTabColorSchemeParams);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setDisplayMode(androidx.browser.trusted.TrustedWebActivityDisplayMode);
+    method @Deprecated public androidx.browser.trusted.TrustedWebActivityIntentBuilder setNavigationBarColor(@ColorInt int);
+    method @Deprecated public androidx.browser.trusted.TrustedWebActivityIntentBuilder setNavigationBarDividerColor(@ColorInt int);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setScreenOrientation(int);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setShareParams(androidx.browser.trusted.sharing.ShareTarget, androidx.browser.trusted.sharing.ShareData);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setSplashScreenParams(android.os.Bundle);
+    method @Deprecated public androidx.browser.trusted.TrustedWebActivityIntentBuilder setToolbarColor(@ColorInt int);
+    field public static final String EXTRA_ADDITIONAL_TRUSTED_ORIGINS = "android.support.customtabs.extra.ADDITIONAL_TRUSTED_ORIGINS";
+    field public static final String EXTRA_DISPLAY_MODE = "androidx.browser.trusted.extra.DISPLAY_MODE";
+    field public static final String EXTRA_SCREEN_ORIENTATION = "androidx.browser.trusted.extra.SCREEN_ORIENTATION";
+    field public static final String EXTRA_SHARE_DATA = "androidx.browser.trusted.extra.SHARE_DATA";
+    field public static final String EXTRA_SHARE_TARGET = "androidx.browser.trusted.extra.SHARE_TARGET";
+    field public static final String EXTRA_SPLASH_SCREEN_PARAMS = "androidx.browser.trusted.EXTRA_SPLASH_SCREEN_PARAMS";
+  }
+
+  public abstract class TrustedWebActivityService extends android.app.Service {
+    ctor public TrustedWebActivityService();
+    method @BinderThread public abstract androidx.browser.trusted.TokenStore getTokenStore();
+    method @BinderThread public boolean onAreNotificationsEnabled(String);
+    method @MainThread public final android.os.IBinder? onBind(android.content.Intent?);
+    method @BinderThread public void onCancelNotification(String, int);
+    method @BinderThread public android.os.Bundle? onExtraCommand(String, android.os.Bundle, androidx.browser.trusted.TrustedWebActivityCallbackRemote?);
+    method @BinderThread public android.os.Bundle onGetSmallIconBitmap();
+    method @BinderThread public int onGetSmallIconId();
+    method @BinderThread @RequiresPermission(android.Manifest.permission.POST_NOTIFICATIONS) public boolean onNotifyNotificationWithChannel(String, int, android.app.Notification, String);
+    method @MainThread public final boolean onUnbind(android.content.Intent?);
+    field public static final String ACTION_TRUSTED_WEB_ACTIVITY_SERVICE = "android.support.customtabs.trusted.TRUSTED_WEB_ACTIVITY_SERVICE";
+    field public static final String KEY_SMALL_ICON_BITMAP = "android.support.customtabs.trusted.SMALL_ICON_BITMAP";
+    field public static final String KEY_SUCCESS = "androidx.browser.trusted.SUCCESS";
+    field public static final String META_DATA_NAME_SMALL_ICON = "android.support.customtabs.trusted.SMALL_ICON";
+    field public static final int SMALL_ICON_NOT_SET = -1; // 0xffffffff
+  }
+
+  public final class TrustedWebActivityServiceConnection {
+    method public boolean areNotificationsEnabled(String) throws android.os.RemoteException;
+    method public void cancel(String, int) throws android.os.RemoteException;
+    method public android.content.ComponentName getComponentName();
+    method public android.graphics.Bitmap? getSmallIconBitmap() throws android.os.RemoteException;
+    method public int getSmallIconId() throws android.os.RemoteException;
+    method public boolean notify(String, int, android.app.Notification, String) throws android.os.RemoteException;
+    method public android.os.Bundle? sendExtraCommand(String, android.os.Bundle, androidx.browser.trusted.TrustedWebActivityCallback?) throws android.os.RemoteException;
+  }
+
+  public final class TrustedWebActivityServiceConnectionPool {
+    method @MainThread public com.google.common.util.concurrent.ListenableFuture<androidx.browser.trusted.TrustedWebActivityServiceConnection!> connect(android.net.Uri, java.util.Set<androidx.browser.trusted.Token!>, java.util.concurrent.Executor);
+    method public static androidx.browser.trusted.TrustedWebActivityServiceConnectionPool create(android.content.Context);
+    method @MainThread public boolean serviceExistsForScope(android.net.Uri, java.util.Set<androidx.browser.trusted.Token!>);
+  }
+
+}
+
+package androidx.browser.trusted.sharing {
+
+  public final class ShareData {
+    ctor public ShareData(String?, String?, java.util.List<android.net.Uri!>?);
+    method public static androidx.browser.trusted.sharing.ShareData fromBundle(android.os.Bundle);
+    method public android.os.Bundle toBundle();
+    field public static final String KEY_TEXT = "androidx.browser.trusted.sharing.KEY_TEXT";
+    field public static final String KEY_TITLE = "androidx.browser.trusted.sharing.KEY_TITLE";
+    field public static final String KEY_URIS = "androidx.browser.trusted.sharing.KEY_URIS";
+    field public final String? text;
+    field public final String? title;
+    field public final java.util.List<android.net.Uri!>? uris;
+  }
+
+  public final class ShareTarget {
+    ctor public ShareTarget(String, String?, String?, androidx.browser.trusted.sharing.ShareTarget.Params);
+    method public static androidx.browser.trusted.sharing.ShareTarget? fromBundle(android.os.Bundle);
+    method public android.os.Bundle toBundle();
+    field public static final String ENCODING_TYPE_MULTIPART = "multipart/form-data";
+    field public static final String ENCODING_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
+    field public static final String KEY_ACTION = "androidx.browser.trusted.sharing.KEY_ACTION";
+    field public static final String KEY_ENCTYPE = "androidx.browser.trusted.sharing.KEY_ENCTYPE";
+    field public static final String KEY_METHOD = "androidx.browser.trusted.sharing.KEY_METHOD";
+    field public static final String KEY_PARAMS = "androidx.browser.trusted.sharing.KEY_PARAMS";
+    field public static final String METHOD_GET = "GET";
+    field public static final String METHOD_POST = "POST";
+    field public final String action;
+    field public final String? encodingType;
+    field public final String? method;
+    field public final androidx.browser.trusted.sharing.ShareTarget.Params params;
+  }
+
+  public static final class ShareTarget.FileFormField {
+    ctor public ShareTarget.FileFormField(String, java.util.List<java.lang.String!>);
+    field public static final String KEY_ACCEPTED_TYPES = "androidx.browser.trusted.sharing.KEY_ACCEPTED_TYPES";
+    field public static final String KEY_NAME = "androidx.browser.trusted.sharing.KEY_FILE_NAME";
+    field public final java.util.List<java.lang.String!> acceptedTypes;
+    field public final String name;
+  }
+
+  public static class ShareTarget.Params {
+    ctor public ShareTarget.Params(String?, String?, java.util.List<androidx.browser.trusted.sharing.ShareTarget.FileFormField!>?);
+    field public static final String KEY_FILES = "androidx.browser.trusted.sharing.KEY_FILES";
+    field public static final String KEY_TEXT = "androidx.browser.trusted.sharing.KEY_TEXT";
+    field public static final String KEY_TITLE = "androidx.browser.trusted.sharing.KEY_TITLE";
+    field public final java.util.List<androidx.browser.trusted.sharing.ShareTarget.FileFormField!>? files;
+    field public final String? text;
+    field public final String? title;
+  }
+
+}
+
+package androidx.browser.trusted.splashscreens {
+
+  public final class SplashScreenParamKey {
+    field public static final String KEY_BACKGROUND_COLOR = "androidx.browser.trusted.trusted.KEY_SPLASH_SCREEN_BACKGROUND_COLOR";
+    field public static final String KEY_FADE_OUT_DURATION_MS = "androidx.browser.trusted.KEY_SPLASH_SCREEN_FADE_OUT_DURATION";
+    field public static final String KEY_IMAGE_TRANSFORMATION_MATRIX = "androidx.browser.trusted.KEY_SPLASH_SCREEN_TRANSFORMATION_MATRIX";
+    field public static final String KEY_SCALE_TYPE = "androidx.browser.trusted.KEY_SPLASH_SCREEN_SCALE_TYPE";
+    field public static final String KEY_VERSION = "androidx.browser.trusted.KEY_SPLASH_SCREEN_VERSION";
+  }
+
+  public final class SplashScreenVersion {
+    field public static final String V1 = "androidx.browser.trusted.category.TrustedWebActivitySplashScreensV1";
+  }
+
+}
+
diff --git a/browser/browser/api/res-1.5.0-beta02.txt b/browser/browser/api/res-1.5.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/browser/browser/api/res-1.5.0-beta02.txt
diff --git a/browser/browser/api/restricted_1.5.0-beta02.txt b/browser/browser/api/restricted_1.5.0-beta02.txt
new file mode 100644
index 0000000..de4bb0c
--- /dev/null
+++ b/browser/browser/api/restricted_1.5.0-beta02.txt
@@ -0,0 +1,480 @@
+// Signature format: 4.0
+package androidx.browser.browseractions {
+
+  @Deprecated public class BrowserActionItem {
+    ctor @Deprecated public BrowserActionItem(String, android.app.PendingIntent, @DrawableRes int);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public BrowserActionItem(String, android.app.PendingIntent, android.net.Uri);
+    ctor @Deprecated public BrowserActionItem(String, android.app.PendingIntent);
+    method @Deprecated public android.app.PendingIntent getAction();
+    method @Deprecated public int getIconId();
+    method @Deprecated public String getTitle();
+  }
+
+  @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class BrowserActionsFallbackMenuView extends android.widget.LinearLayout {
+    ctor @Deprecated public BrowserActionsFallbackMenuView(android.content.Context, android.util.AttributeSet);
+  }
+
+  @Deprecated public class BrowserActionsIntent {
+    method @Deprecated public static String? getCreatorPackageName(android.content.Intent);
+    method @Deprecated public android.content.Intent getIntent();
+    method @Deprecated public static String? getUntrustedCreatorPackageName(android.content.Intent);
+    method @Deprecated public static void launchIntent(android.content.Context, android.content.Intent);
+    method @Deprecated public static void openBrowserAction(android.content.Context, android.net.Uri);
+    method @Deprecated public static void openBrowserAction(android.content.Context, android.net.Uri, int, java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem!>, android.app.PendingIntent);
+    method @Deprecated public static java.util.List<androidx.browser.browseractions.BrowserActionItem!> parseBrowserActionItems(java.util.ArrayList<android.os.Bundle!>);
+    field @Deprecated public static final String ACTION_BROWSER_ACTIONS_OPEN = "androidx.browser.browseractions.browser_action_open";
+    field @Deprecated public static final String EXTRA_APP_ID = "androidx.browser.browseractions.APP_ID";
+    field @Deprecated public static final String EXTRA_MENU_ITEMS = "androidx.browser.browseractions.extra.MENU_ITEMS";
+    field @Deprecated public static final String EXTRA_SELECTED_ACTION_PENDING_INTENT = "androidx.browser.browseractions.extra.SELECTED_ACTION_PENDING_INTENT";
+    field @Deprecated public static final String EXTRA_TYPE = "androidx.browser.browseractions.extra.TYPE";
+    field @Deprecated public static final int ITEM_COPY = 3; // 0x3
+    field @Deprecated public static final int ITEM_DOWNLOAD = 2; // 0x2
+    field @Deprecated public static final int ITEM_INVALID_ITEM = -1; // 0xffffffff
+    field @Deprecated public static final int ITEM_OPEN_IN_INCOGNITO = 1; // 0x1
+    field @Deprecated public static final int ITEM_OPEN_IN_NEW_TAB = 0; // 0x0
+    field @Deprecated public static final int ITEM_SHARE = 4; // 0x4
+    field @Deprecated public static final String KEY_ACTION = "androidx.browser.browseractions.ACTION";
+    field @Deprecated public static final String KEY_ICON_ID = "androidx.browser.browseractions.ICON_ID";
+    field @Deprecated public static final String KEY_TITLE = "androidx.browser.browseractions.TITLE";
+    field @Deprecated public static final int MAX_CUSTOM_ITEMS = 5; // 0x5
+    field @Deprecated public static final int URL_TYPE_AUDIO = 3; // 0x3
+    field @Deprecated public static final int URL_TYPE_FILE = 4; // 0x4
+    field @Deprecated public static final int URL_TYPE_IMAGE = 1; // 0x1
+    field @Deprecated public static final int URL_TYPE_NONE = 0; // 0x0
+    field @Deprecated public static final int URL_TYPE_PLUGIN = 5; // 0x5
+    field @Deprecated public static final int URL_TYPE_VIDEO = 2; // 0x2
+  }
+
+  @Deprecated @IntDef({androidx.browser.browseractions.BrowserActionsIntent.ITEM_INVALID_ITEM, androidx.browser.browseractions.BrowserActionsIntent.ITEM_OPEN_IN_NEW_TAB, androidx.browser.browseractions.BrowserActionsIntent.ITEM_OPEN_IN_INCOGNITO, androidx.browser.browseractions.BrowserActionsIntent.ITEM_DOWNLOAD, androidx.browser.browseractions.BrowserActionsIntent.ITEM_COPY, androidx.browser.browseractions.BrowserActionsIntent.ITEM_SHARE}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface BrowserActionsIntent.BrowserActionsItemId {
+  }
+
+  @Deprecated @IntDef({androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_NONE, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_IMAGE, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_VIDEO, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_AUDIO, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_FILE, androidx.browser.browseractions.BrowserActionsIntent.URL_TYPE_PLUGIN}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface BrowserActionsIntent.BrowserActionsUrlType {
+  }
+
+  @Deprecated public static final class BrowserActionsIntent.Builder {
+    ctor @Deprecated public BrowserActionsIntent.Builder(android.content.Context, android.net.Uri);
+    method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent build();
+    method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder setCustomItems(java.util.ArrayList<androidx.browser.browseractions.BrowserActionItem!>);
+    method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder setCustomItems(androidx.browser.browseractions.BrowserActionItem!...);
+    method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder setOnItemSelectedAction(android.app.PendingIntent);
+    method @Deprecated public androidx.browser.browseractions.BrowserActionsIntent.Builder setUrlType(@androidx.browser.browseractions.BrowserActionsIntent.BrowserActionsUrlType int);
+  }
+
+}
+
+package androidx.browser.customtabs {
+
+  public final class CustomTabColorSchemeParams {
+    field @ColorInt public final Integer? navigationBarColor;
+    field @ColorInt public final Integer? navigationBarDividerColor;
+    field @ColorInt public final Integer? secondaryToolbarColor;
+    field @ColorInt public final Integer? toolbarColor;
+  }
+
+  public static final class CustomTabColorSchemeParams.Builder {
+    ctor public CustomTabColorSchemeParams.Builder();
+    method public androidx.browser.customtabs.CustomTabColorSchemeParams build();
+    method public androidx.browser.customtabs.CustomTabColorSchemeParams.Builder setNavigationBarColor(@ColorInt int);
+    method public androidx.browser.customtabs.CustomTabColorSchemeParams.Builder setNavigationBarDividerColor(@ColorInt int);
+    method public androidx.browser.customtabs.CustomTabColorSchemeParams.Builder setSecondaryToolbarColor(@ColorInt int);
+    method public androidx.browser.customtabs.CustomTabColorSchemeParams.Builder setToolbarColor(@ColorInt int);
+  }
+
+  public class CustomTabsCallback {
+    ctor public CustomTabsCallback();
+    method public void extraCallback(String, android.os.Bundle?);
+    method public android.os.Bundle? extraCallbackWithResult(String, android.os.Bundle?);
+    method public void onActivityResized(@Dimension(unit=androidx.annotation.Dimension.PX) int, @Dimension(unit=androidx.annotation.Dimension.PX) int, android.os.Bundle);
+    method public void onMessageChannelReady(android.os.Bundle?);
+    method public void onNavigationEvent(int, android.os.Bundle?);
+    method public void onPostMessage(String, android.os.Bundle?);
+    method public void onRelationshipValidationResult(@androidx.browser.customtabs.CustomTabsService.Relation int, android.net.Uri, boolean, android.os.Bundle?);
+    field public static final int NAVIGATION_ABORTED = 4; // 0x4
+    field public static final int NAVIGATION_FAILED = 3; // 0x3
+    field public static final int NAVIGATION_FINISHED = 2; // 0x2
+    field public static final int NAVIGATION_STARTED = 1; // 0x1
+    field public static final int TAB_HIDDEN = 6; // 0x6
+    field public static final int TAB_SHOWN = 5; // 0x5
+  }
+
+  public class CustomTabsClient {
+    method public static boolean bindCustomTabsService(android.content.Context, String?, androidx.browser.customtabs.CustomTabsServiceConnection);
+    method public static boolean bindCustomTabsServicePreservePriority(android.content.Context, String?, androidx.browser.customtabs.CustomTabsServiceConnection);
+    method public static boolean connectAndInitialize(android.content.Context, String);
+    method public android.os.Bundle? extraCommand(String, android.os.Bundle?);
+    method public static String? getPackageName(android.content.Context, java.util.List<java.lang.String!>?);
+    method public static String? getPackageName(android.content.Context, java.util.List<java.lang.String!>?, boolean);
+    method public androidx.browser.customtabs.CustomTabsSession? newSession(androidx.browser.customtabs.CustomTabsCallback?);
+    method public androidx.browser.customtabs.CustomTabsSession? newSession(androidx.browser.customtabs.CustomTabsCallback?, int);
+    method public boolean warmup(long);
+  }
+
+  public final class CustomTabsIntent {
+    method public static int getActivityResizeBehavior(android.content.Intent);
+    method public static int getCloseButtonPosition(android.content.Intent);
+    method public static androidx.browser.customtabs.CustomTabColorSchemeParams getColorSchemeParams(android.content.Intent, int);
+    method @Dimension(unit=androidx.annotation.Dimension.PX) public static int getInitialActivityHeightPx(android.content.Intent);
+    method public static int getMaxToolbarItems();
+    method @Dimension(unit=androidx.annotation.Dimension.DP) public static int getToolbarCornerRadiusDp(android.content.Intent);
+    method public void launchUrl(android.content.Context, android.net.Uri);
+    method public static android.content.Intent setAlwaysUseBrowserUI(android.content.Intent?);
+    method public static boolean shouldAlwaysUseBrowserUI(android.content.Intent);
+    field public static final int ACTIVITY_HEIGHT_ADJUSTABLE = 1; // 0x1
+    field public static final int ACTIVITY_HEIGHT_DEFAULT = 0; // 0x0
+    field public static final int ACTIVITY_HEIGHT_FIXED = 2; // 0x2
+    field public static final int CLOSE_BUTTON_POSITION_DEFAULT = 0; // 0x0
+    field public static final int CLOSE_BUTTON_POSITION_END = 2; // 0x2
+    field public static final int CLOSE_BUTTON_POSITION_START = 1; // 0x1
+    field public static final int COLOR_SCHEME_DARK = 2; // 0x2
+    field public static final int COLOR_SCHEME_LIGHT = 1; // 0x1
+    field public static final int COLOR_SCHEME_SYSTEM = 0; // 0x0
+    field public static final String EXTRA_ACTION_BUTTON_BUNDLE = "android.support.customtabs.extra.ACTION_BUTTON_BUNDLE";
+    field public static final String EXTRA_ACTIVITY_HEIGHT_RESIZE_BEHAVIOR = "androidx.browser.customtabs.extra.ACTIVITY_HEIGHT_RESIZE_BEHAVIOR";
+    field public static final String EXTRA_CLOSE_BUTTON_ICON = "android.support.customtabs.extra.CLOSE_BUTTON_ICON";
+    field public static final String EXTRA_CLOSE_BUTTON_POSITION = "androidx.browser.customtabs.extra.CLOSE_BUTTON_POSITION";
+    field public static final String EXTRA_COLOR_SCHEME = "androidx.browser.customtabs.extra.COLOR_SCHEME";
+    field public static final String EXTRA_COLOR_SCHEME_PARAMS = "androidx.browser.customtabs.extra.COLOR_SCHEME_PARAMS";
+    field @Deprecated public static final String EXTRA_DEFAULT_SHARE_MENU_ITEM = "android.support.customtabs.extra.SHARE_MENU_ITEM";
+    field public static final String EXTRA_ENABLE_INSTANT_APPS = "android.support.customtabs.extra.EXTRA_ENABLE_INSTANT_APPS";
+    field public static final String EXTRA_ENABLE_URLBAR_HIDING = "android.support.customtabs.extra.ENABLE_URLBAR_HIDING";
+    field public static final String EXTRA_EXIT_ANIMATION_BUNDLE = "android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE";
+    field public static final String EXTRA_INITIAL_ACTIVITY_HEIGHT_PX = "androidx.browser.customtabs.extra.INITIAL_ACTIVITY_HEIGHT_PX";
+    field public static final String EXTRA_MENU_ITEMS = "android.support.customtabs.extra.MENU_ITEMS";
+    field public static final String EXTRA_NAVIGATION_BAR_COLOR = "androidx.browser.customtabs.extra.NAVIGATION_BAR_COLOR";
+    field public static final String EXTRA_NAVIGATION_BAR_DIVIDER_COLOR = "androidx.browser.customtabs.extra.NAVIGATION_BAR_DIVIDER_COLOR";
+    field public static final String EXTRA_REMOTEVIEWS = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS";
+    field public static final String EXTRA_REMOTEVIEWS_CLICKED_ID = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_CLICKED_ID";
+    field public static final String EXTRA_REMOTEVIEWS_PENDINGINTENT = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_PENDINGINTENT";
+    field public static final String EXTRA_REMOTEVIEWS_VIEW_IDS = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_VIEW_IDS";
+    field public static final String EXTRA_SECONDARY_TOOLBAR_COLOR = "android.support.customtabs.extra.SECONDARY_TOOLBAR_COLOR";
+    field public static final String EXTRA_SESSION = "android.support.customtabs.extra.SESSION";
+    field public static final String EXTRA_SHARE_STATE = "androidx.browser.customtabs.extra.SHARE_STATE";
+    field public static final String EXTRA_TINT_ACTION_BUTTON = "android.support.customtabs.extra.TINT_ACTION_BUTTON";
+    field public static final String EXTRA_TITLE_VISIBILITY_STATE = "android.support.customtabs.extra.TITLE_VISIBILITY";
+    field public static final String EXTRA_TOOLBAR_COLOR = "android.support.customtabs.extra.TOOLBAR_COLOR";
+    field public static final String EXTRA_TOOLBAR_CORNER_RADIUS_DP = "androidx.browser.customtabs.extra.TOOLBAR_CORNER_RADIUS_DP";
+    field public static final String EXTRA_TOOLBAR_ITEMS = "android.support.customtabs.extra.TOOLBAR_ITEMS";
+    field public static final String KEY_DESCRIPTION = "android.support.customtabs.customaction.DESCRIPTION";
+    field public static final String KEY_ICON = "android.support.customtabs.customaction.ICON";
+    field public static final String KEY_ID = "android.support.customtabs.customaction.ID";
+    field public static final String KEY_MENU_ITEM_TITLE = "android.support.customtabs.customaction.MENU_ITEM_TITLE";
+    field public static final String KEY_PENDING_INTENT = "android.support.customtabs.customaction.PENDING_INTENT";
+    field public static final int NO_TITLE = 0; // 0x0
+    field public static final int SHARE_STATE_DEFAULT = 0; // 0x0
+    field public static final int SHARE_STATE_OFF = 2; // 0x2
+    field public static final int SHARE_STATE_ON = 1; // 0x1
+    field public static final int SHOW_PAGE_TITLE = 1; // 0x1
+    field public static final int TOOLBAR_ACTION_BUTTON_ID = 0; // 0x0
+    field public final android.content.Intent intent;
+    field public final android.os.Bundle? startAnimationBundle;
+  }
+
+  public static final class CustomTabsIntent.Builder {
+    ctor public CustomTabsIntent.Builder();
+    ctor public CustomTabsIntent.Builder(androidx.browser.customtabs.CustomTabsSession?);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder addDefaultShareMenuItem();
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder addMenuItem(String, android.app.PendingIntent);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder addToolbarItem(int, android.graphics.Bitmap, String, android.app.PendingIntent) throws java.lang.IllegalStateException;
+    method public androidx.browser.customtabs.CustomTabsIntent build();
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder enableUrlBarHiding();
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, String, android.app.PendingIntent, boolean);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, String, android.app.PendingIntent);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setCloseButtonIcon(android.graphics.Bitmap);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setCloseButtonPosition(int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setColorScheme(int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setColorSchemeParams(int, androidx.browser.customtabs.CustomTabColorSchemeParams);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setDefaultColorSchemeParams(androidx.browser.customtabs.CustomTabColorSchemeParams);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setDefaultShareMenuItemEnabled(boolean);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setExitAnimations(android.content.Context, @AnimRes int, @AnimRes int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setInitialActivityHeightPx(@Dimension(unit=androidx.annotation.Dimension.PX) int, int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setInitialActivityHeightPx(@Dimension(unit=androidx.annotation.Dimension.PX) int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setInstantAppsEnabled(boolean);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setNavigationBarColor(@ColorInt int);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setNavigationBarDividerColor(@ColorInt int);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setSecondaryToolbarColor(@ColorInt int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setSecondaryToolbarViews(android.widget.RemoteViews, int[]?, android.app.PendingIntent?);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setSession(androidx.browser.customtabs.CustomTabsSession);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setShareState(int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setShowTitle(boolean);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setStartAnimations(android.content.Context, @AnimRes int, @AnimRes int);
+    method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setToolbarColor(@ColorInt int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setToolbarCornerRadiusDp(@Dimension(unit=androidx.annotation.Dimension.DP) int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setUrlBarHidingEnabled(boolean);
+  }
+
+  public abstract class CustomTabsService extends android.app.Service {
+    ctor public CustomTabsService();
+    method protected boolean cleanUpSession(androidx.browser.customtabs.CustomTabsSessionToken);
+    method protected abstract android.os.Bundle? extraCommand(String, android.os.Bundle?);
+    method protected abstract boolean mayLaunchUrl(androidx.browser.customtabs.CustomTabsSessionToken, android.net.Uri?, android.os.Bundle?, java.util.List<android.os.Bundle!>?);
+    method protected abstract boolean newSession(androidx.browser.customtabs.CustomTabsSessionToken);
+    method public android.os.IBinder onBind(android.content.Intent?);
+    method @androidx.browser.customtabs.CustomTabsService.Result protected abstract int postMessage(androidx.browser.customtabs.CustomTabsSessionToken, String, android.os.Bundle?);
+    method protected abstract boolean receiveFile(androidx.browser.customtabs.CustomTabsSessionToken, android.net.Uri, int, android.os.Bundle?);
+    method protected abstract boolean requestPostMessageChannel(androidx.browser.customtabs.CustomTabsSessionToken, android.net.Uri);
+    method protected abstract boolean updateVisuals(androidx.browser.customtabs.CustomTabsSessionToken, android.os.Bundle?);
+    method protected abstract boolean validateRelationship(androidx.browser.customtabs.CustomTabsSessionToken, @androidx.browser.customtabs.CustomTabsService.Relation int, android.net.Uri, android.os.Bundle?);
+    method protected abstract boolean warmup(long);
+    field public static final String ACTION_CUSTOM_TABS_CONNECTION = "android.support.customtabs.action.CustomTabsService";
+    field public static final String CATEGORY_COLOR_SCHEME_CUSTOMIZATION = "androidx.browser.customtabs.category.ColorSchemeCustomization";
+    field public static final String CATEGORY_NAVBAR_COLOR_CUSTOMIZATION = "androidx.browser.customtabs.category.NavBarColorCustomization";
+    field public static final String CATEGORY_TRUSTED_WEB_ACTIVITY_IMMERSIVE_MODE = "androidx.browser.trusted.category.ImmersiveMode";
+    field public static final String CATEGORY_WEB_SHARE_TARGET_V2 = "androidx.browser.trusted.category.WebShareTargetV2";
+    field public static final int FILE_PURPOSE_TRUSTED_WEB_ACTIVITY_SPLASH_IMAGE = 1; // 0x1
+    field public static final String KEY_SUCCESS = "androidx.browser.customtabs.SUCCESS";
+    field public static final String KEY_URL = "android.support.customtabs.otherurls.URL";
+    field public static final int RELATION_HANDLE_ALL_URLS = 2; // 0x2
+    field public static final int RELATION_USE_AS_ORIGIN = 1; // 0x1
+    field public static final int RESULT_FAILURE_DISALLOWED = -1; // 0xffffffff
+    field public static final int RESULT_FAILURE_MESSAGING_ERROR = -3; // 0xfffffffd
+    field public static final int RESULT_FAILURE_REMOTE_ERROR = -2; // 0xfffffffe
+    field public static final int RESULT_SUCCESS = 0; // 0x0
+    field public static final String TRUSTED_WEB_ACTIVITY_CATEGORY = "androidx.browser.trusted.category.TrustedWebActivities";
+  }
+
+  @IntDef({androidx.browser.customtabs.CustomTabsService.RELATION_USE_AS_ORIGIN, androidx.browser.customtabs.CustomTabsService.RELATION_HANDLE_ALL_URLS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface CustomTabsService.Relation {
+  }
+
+  @IntDef({androidx.browser.customtabs.CustomTabsService.RESULT_SUCCESS, androidx.browser.customtabs.CustomTabsService.RESULT_FAILURE_DISALLOWED, androidx.browser.customtabs.CustomTabsService.RESULT_FAILURE_REMOTE_ERROR, androidx.browser.customtabs.CustomTabsService.RESULT_FAILURE_MESSAGING_ERROR}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface CustomTabsService.Result {
+  }
+
+  public abstract class CustomTabsServiceConnection implements android.content.ServiceConnection {
+    ctor public CustomTabsServiceConnection();
+    method public abstract void onCustomTabsServiceConnected(android.content.ComponentName, androidx.browser.customtabs.CustomTabsClient);
+    method public final void onServiceConnected(android.content.ComponentName, android.os.IBinder);
+  }
+
+  public final class CustomTabsSession {
+    method @VisibleForTesting public static androidx.browser.customtabs.CustomTabsSession createMockSessionForTesting(android.content.ComponentName);
+    method public boolean mayLaunchUrl(android.net.Uri?, android.os.Bundle?, java.util.List<android.os.Bundle!>?);
+    method @androidx.browser.customtabs.CustomTabsService.Result public int postMessage(String, android.os.Bundle?);
+    method public boolean receiveFile(android.net.Uri, int, android.os.Bundle?);
+    method public boolean requestPostMessageChannel(android.net.Uri);
+    method public boolean setActionButton(android.graphics.Bitmap, String);
+    method public boolean setSecondaryToolbarViews(android.widget.RemoteViews?, int[]?, android.app.PendingIntent?);
+    method @Deprecated public boolean setToolbarItem(int, android.graphics.Bitmap, String);
+    method public boolean validateRelationship(@androidx.browser.customtabs.CustomTabsService.Relation int, android.net.Uri, android.os.Bundle?);
+  }
+
+  public class CustomTabsSessionToken {
+    method public static androidx.browser.customtabs.CustomTabsSessionToken createMockSessionTokenForTesting();
+    method public androidx.browser.customtabs.CustomTabsCallback? getCallback();
+    method public static androidx.browser.customtabs.CustomTabsSessionToken? getSessionTokenFromIntent(android.content.Intent);
+    method public boolean isAssociatedWith(androidx.browser.customtabs.CustomTabsSession);
+  }
+
+  public class PostMessageService extends android.app.Service {
+    ctor public PostMessageService();
+    method public android.os.IBinder onBind(android.content.Intent?);
+  }
+
+  public abstract class PostMessageServiceConnection implements android.content.ServiceConnection {
+    ctor public PostMessageServiceConnection(androidx.browser.customtabs.CustomTabsSessionToken);
+    method public boolean bindSessionToPostMessageService(android.content.Context, String);
+    method public final boolean notifyMessageChannelReady(android.os.Bundle?);
+    method public void onPostMessageServiceConnected();
+    method public void onPostMessageServiceDisconnected();
+    method public final void onServiceConnected(android.content.ComponentName, android.os.IBinder);
+    method public final void onServiceDisconnected(android.content.ComponentName);
+    method public final boolean postMessage(String, android.os.Bundle?);
+    method public void unbindFromContext(android.content.Context);
+  }
+
+  public class TrustedWebUtils {
+    method public static boolean areSplashScreensSupported(android.content.Context, String, String);
+    method @Deprecated public static void launchAsTrustedWebActivity(android.content.Context, androidx.browser.customtabs.CustomTabsIntent, android.net.Uri);
+    method @WorkerThread public static boolean transferSplashImage(android.content.Context, java.io.File, String, String, androidx.browser.customtabs.CustomTabsSession);
+    field public static final String EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY = "android.support.customtabs.extra.LAUNCH_AS_TRUSTED_WEB_ACTIVITY";
+  }
+
+}
+
+package androidx.browser.trusted {
+
+  public final class ScreenOrientation {
+    field public static final int ANY = 5; // 0x5
+    field public static final int DEFAULT = 0; // 0x0
+    field public static final int LANDSCAPE = 6; // 0x6
+    field public static final int LANDSCAPE_PRIMARY = 3; // 0x3
+    field public static final int LANDSCAPE_SECONDARY = 4; // 0x4
+    field public static final int NATURAL = 8; // 0x8
+    field public static final int PORTRAIT = 7; // 0x7
+    field public static final int PORTRAIT_PRIMARY = 1; // 0x1
+    field public static final int PORTRAIT_SECONDARY = 2; // 0x2
+  }
+
+  public final class Token {
+    method public static androidx.browser.trusted.Token? create(String, android.content.pm.PackageManager);
+    method public static androidx.browser.trusted.Token deserialize(byte[]);
+    method public boolean matches(String, android.content.pm.PackageManager);
+    method public byte[] serialize();
+  }
+
+  public interface TokenStore {
+    method @BinderThread public androidx.browser.trusted.Token? load();
+    method @WorkerThread public void store(androidx.browser.trusted.Token?);
+  }
+
+  public abstract class TrustedWebActivityCallback {
+    ctor public TrustedWebActivityCallback();
+    method public abstract void onExtraCallback(String, android.os.Bundle?);
+  }
+
+  public class TrustedWebActivityCallbackRemote {
+    method public void runExtraCallback(String, android.os.Bundle) throws android.os.RemoteException;
+  }
+
+  public interface TrustedWebActivityDisplayMode {
+    method public static androidx.browser.trusted.TrustedWebActivityDisplayMode fromBundle(android.os.Bundle);
+    method public android.os.Bundle toBundle();
+    field public static final String KEY_ID = "androidx.browser.trusted.displaymode.KEY_ID";
+  }
+
+  public static class TrustedWebActivityDisplayMode.DefaultMode implements androidx.browser.trusted.TrustedWebActivityDisplayMode {
+    ctor public TrustedWebActivityDisplayMode.DefaultMode();
+    method public android.os.Bundle toBundle();
+  }
+
+  public static class TrustedWebActivityDisplayMode.ImmersiveMode implements androidx.browser.trusted.TrustedWebActivityDisplayMode {
+    ctor public TrustedWebActivityDisplayMode.ImmersiveMode(boolean, int);
+    method public boolean isSticky();
+    method public int layoutInDisplayCutoutMode();
+    method public android.os.Bundle toBundle();
+    field public static final String KEY_CUTOUT_MODE = "androidx.browser.trusted.displaymode.KEY_CUTOUT_MODE";
+    field public static final String KEY_STICKY = "androidx.browser.trusted.displaymode.KEY_STICKY";
+  }
+
+  public final class TrustedWebActivityIntent {
+    method public android.content.Intent getIntent();
+    method public void launchTrustedWebActivity(android.content.Context);
+  }
+
+  public class TrustedWebActivityIntentBuilder {
+    ctor public TrustedWebActivityIntentBuilder(android.net.Uri);
+    method public androidx.browser.trusted.TrustedWebActivityIntent build(androidx.browser.customtabs.CustomTabsSession);
+    method public androidx.browser.customtabs.CustomTabsIntent buildCustomTabsIntent();
+    method public androidx.browser.trusted.TrustedWebActivityDisplayMode getDisplayMode();
+    method public android.net.Uri getUri();
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setAdditionalTrustedOrigins(java.util.List<java.lang.String!>);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setColorScheme(int);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setColorSchemeParams(int, androidx.browser.customtabs.CustomTabColorSchemeParams);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setDefaultColorSchemeParams(androidx.browser.customtabs.CustomTabColorSchemeParams);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setDisplayMode(androidx.browser.trusted.TrustedWebActivityDisplayMode);
+    method @Deprecated public androidx.browser.trusted.TrustedWebActivityIntentBuilder setNavigationBarColor(@ColorInt int);
+    method @Deprecated public androidx.browser.trusted.TrustedWebActivityIntentBuilder setNavigationBarDividerColor(@ColorInt int);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setScreenOrientation(int);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setShareParams(androidx.browser.trusted.sharing.ShareTarget, androidx.browser.trusted.sharing.ShareData);
+    method public androidx.browser.trusted.TrustedWebActivityIntentBuilder setSplashScreenParams(android.os.Bundle);
+    method @Deprecated public androidx.browser.trusted.TrustedWebActivityIntentBuilder setToolbarColor(@ColorInt int);
+    field public static final String EXTRA_ADDITIONAL_TRUSTED_ORIGINS = "android.support.customtabs.extra.ADDITIONAL_TRUSTED_ORIGINS";
+    field public static final String EXTRA_DISPLAY_MODE = "androidx.browser.trusted.extra.DISPLAY_MODE";
+    field public static final String EXTRA_SCREEN_ORIENTATION = "androidx.browser.trusted.extra.SCREEN_ORIENTATION";
+    field public static final String EXTRA_SHARE_DATA = "androidx.browser.trusted.extra.SHARE_DATA";
+    field public static final String EXTRA_SHARE_TARGET = "androidx.browser.trusted.extra.SHARE_TARGET";
+    field public static final String EXTRA_SPLASH_SCREEN_PARAMS = "androidx.browser.trusted.EXTRA_SPLASH_SCREEN_PARAMS";
+  }
+
+  public abstract class TrustedWebActivityService extends android.app.Service {
+    ctor public TrustedWebActivityService();
+    method @BinderThread public abstract androidx.browser.trusted.TokenStore getTokenStore();
+    method @BinderThread public boolean onAreNotificationsEnabled(String);
+    method @MainThread public final android.os.IBinder? onBind(android.content.Intent?);
+    method @BinderThread public void onCancelNotification(String, int);
+    method @BinderThread public android.os.Bundle? onExtraCommand(String, android.os.Bundle, androidx.browser.trusted.TrustedWebActivityCallbackRemote?);
+    method @BinderThread public android.os.Bundle onGetSmallIconBitmap();
+    method @BinderThread public int onGetSmallIconId();
+    method @BinderThread @RequiresPermission(android.Manifest.permission.POST_NOTIFICATIONS) public boolean onNotifyNotificationWithChannel(String, int, android.app.Notification, String);
+    method @MainThread public final boolean onUnbind(android.content.Intent?);
+    field public static final String ACTION_TRUSTED_WEB_ACTIVITY_SERVICE = "android.support.customtabs.trusted.TRUSTED_WEB_ACTIVITY_SERVICE";
+    field public static final String KEY_SMALL_ICON_BITMAP = "android.support.customtabs.trusted.SMALL_ICON_BITMAP";
+    field public static final String KEY_SUCCESS = "androidx.browser.trusted.SUCCESS";
+    field public static final String META_DATA_NAME_SMALL_ICON = "android.support.customtabs.trusted.SMALL_ICON";
+    field public static final int SMALL_ICON_NOT_SET = -1; // 0xffffffff
+  }
+
+  public final class TrustedWebActivityServiceConnection {
+    method public boolean areNotificationsEnabled(String) throws android.os.RemoteException;
+    method public void cancel(String, int) throws android.os.RemoteException;
+    method public android.content.ComponentName getComponentName();
+    method public android.graphics.Bitmap? getSmallIconBitmap() throws android.os.RemoteException;
+    method public int getSmallIconId() throws android.os.RemoteException;
+    method public boolean notify(String, int, android.app.Notification, String) throws android.os.RemoteException;
+    method public android.os.Bundle? sendExtraCommand(String, android.os.Bundle, androidx.browser.trusted.TrustedWebActivityCallback?) throws android.os.RemoteException;
+  }
+
+  public final class TrustedWebActivityServiceConnectionPool {
+    method @MainThread public com.google.common.util.concurrent.ListenableFuture<androidx.browser.trusted.TrustedWebActivityServiceConnection!> connect(android.net.Uri, java.util.Set<androidx.browser.trusted.Token!>, java.util.concurrent.Executor);
+    method public static androidx.browser.trusted.TrustedWebActivityServiceConnectionPool create(android.content.Context);
+    method @MainThread public boolean serviceExistsForScope(android.net.Uri, java.util.Set<androidx.browser.trusted.Token!>);
+  }
+
+}
+
+package androidx.browser.trusted.sharing {
+
+  public final class ShareData {
+    ctor public ShareData(String?, String?, java.util.List<android.net.Uri!>?);
+    method public static androidx.browser.trusted.sharing.ShareData fromBundle(android.os.Bundle);
+    method public android.os.Bundle toBundle();
+    field public static final String KEY_TEXT = "androidx.browser.trusted.sharing.KEY_TEXT";
+    field public static final String KEY_TITLE = "androidx.browser.trusted.sharing.KEY_TITLE";
+    field public static final String KEY_URIS = "androidx.browser.trusted.sharing.KEY_URIS";
+    field public final String? text;
+    field public final String? title;
+    field public final java.util.List<android.net.Uri!>? uris;
+  }
+
+  public final class ShareTarget {
+    ctor public ShareTarget(String, String?, String?, androidx.browser.trusted.sharing.ShareTarget.Params);
+    method public static androidx.browser.trusted.sharing.ShareTarget? fromBundle(android.os.Bundle);
+    method public android.os.Bundle toBundle();
+    field public static final String ENCODING_TYPE_MULTIPART = "multipart/form-data";
+    field public static final String ENCODING_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
+    field public static final String KEY_ACTION = "androidx.browser.trusted.sharing.KEY_ACTION";
+    field public static final String KEY_ENCTYPE = "androidx.browser.trusted.sharing.KEY_ENCTYPE";
+    field public static final String KEY_METHOD = "androidx.browser.trusted.sharing.KEY_METHOD";
+    field public static final String KEY_PARAMS = "androidx.browser.trusted.sharing.KEY_PARAMS";
+    field public static final String METHOD_GET = "GET";
+    field public static final String METHOD_POST = "POST";
+    field public final String action;
+    field public final String? encodingType;
+    field public final String? method;
+    field public final androidx.browser.trusted.sharing.ShareTarget.Params params;
+  }
+
+  public static final class ShareTarget.FileFormField {
+    ctor public ShareTarget.FileFormField(String, java.util.List<java.lang.String!>);
+    field public static final String KEY_ACCEPTED_TYPES = "androidx.browser.trusted.sharing.KEY_ACCEPTED_TYPES";
+    field public static final String KEY_NAME = "androidx.browser.trusted.sharing.KEY_FILE_NAME";
+    field public final java.util.List<java.lang.String!> acceptedTypes;
+    field public final String name;
+  }
+
+  public static class ShareTarget.Params {
+    ctor public ShareTarget.Params(String?, String?, java.util.List<androidx.browser.trusted.sharing.ShareTarget.FileFormField!>?);
+    field public static final String KEY_FILES = "androidx.browser.trusted.sharing.KEY_FILES";
+    field public static final String KEY_TEXT = "androidx.browser.trusted.sharing.KEY_TEXT";
+    field public static final String KEY_TITLE = "androidx.browser.trusted.sharing.KEY_TITLE";
+    field public final java.util.List<androidx.browser.trusted.sharing.ShareTarget.FileFormField!>? files;
+    field public final String? text;
+    field public final String? title;
+  }
+
+}
+
+package androidx.browser.trusted.splashscreens {
+
+  public final class SplashScreenParamKey {
+    field public static final String KEY_BACKGROUND_COLOR = "androidx.browser.trusted.trusted.KEY_SPLASH_SCREEN_BACKGROUND_COLOR";
+    field public static final String KEY_FADE_OUT_DURATION_MS = "androidx.browser.trusted.KEY_SPLASH_SCREEN_FADE_OUT_DURATION";
+    field public static final String KEY_IMAGE_TRANSFORMATION_MATRIX = "androidx.browser.trusted.KEY_SPLASH_SCREEN_TRANSFORMATION_MATRIX";
+    field public static final String KEY_SCALE_TYPE = "androidx.browser.trusted.KEY_SPLASH_SCREEN_SCALE_TYPE";
+    field public static final String KEY_VERSION = "androidx.browser.trusted.KEY_SPLASH_SCREEN_VERSION";
+  }
+
+  public final class SplashScreenVersion {
+    field public static final String V1 = "androidx.browser.trusted.category.TrustedWebActivitySplashScreensV1";
+  }
+
+}
+
diff --git a/buildSrc-tests/src/test/kotlin/androidx/build/LibraryVersionsServiceTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/LibraryVersionsServiceTest.kt
index 892ef6b..85d9997 100644
--- a/buildSrc-tests/src/test/kotlin/androidx/build/LibraryVersionsServiceTest.kt
+++ b/buildSrc-tests/src/test/kotlin/androidx/build/LibraryVersionsServiceTest.kt
@@ -17,6 +17,8 @@
 package androidx.build
 
 import com.google.common.truth.Truth.assertThat
+import org.gradle.api.Project
+import org.gradle.kotlin.dsl.create
 import org.gradle.testfixtures.ProjectBuilder
 import org.junit.Rule
 import org.junit.Test
@@ -237,13 +239,160 @@
         ).isEqualTo(null)
     }
 
+    @Test
+    fun androidxExtension_noAtomicGroup() {
+        runAndroidExtensionTest(
+            projectPath = "myGroup:project1",
+            tomlFile = """
+                [versions]
+                [groups]
+                G1 = { group = "androidx.myGroup" }
+            """.trimIndent(),
+            validateWithKmp = { extension ->
+                extension.mavenVersion = Version("1.0.0")
+                extension.mavenMultiplatformVersion = Version("1.0.0-dev01")
+                assertThat(
+                    extension.mavenGroup
+                ).isEqualTo(
+                    LibraryGroup("androidx.myGroup", atomicGroupVersion = null)
+                )
+                assertThat(
+                    extension.project.version
+                ).isEqualTo(Version("1.0.0-dev01"))
+                extension.validateMavenVersion()
+            },
+            validateWithoutKmp = { extension ->
+                extension.mavenVersion = Version("1.0.0")
+                extension.mavenMultiplatformVersion = Version("1.0.0-dev01")
+                assertThat(
+                    extension.mavenGroup
+                ).isEqualTo(
+                    LibraryGroup("androidx.myGroup", atomicGroupVersion = null)
+                )
+                assertThat(
+                    extension.project.version
+                ).isEqualTo(Version("1.0.0"))
+                extension.validateMavenVersion()
+            }
+        )
+    }
+
+    @Test
+    fun androidxExtension_noAtomicGroup_setKmpVersionFirst() {
+        runAndroidExtensionTest(
+            projectPath = "myGroup:project1",
+            tomlFile = """
+                [versions]
+                [groups]
+                G1 = { group = "androidx.myGroup" }
+            """.trimIndent(),
+            validateWithKmp = { extension ->
+                extension.mavenMultiplatformVersion = Version("1.0.0-dev01")
+                extension.mavenVersion = Version("1.0.0")
+                assertThat(
+                    extension.mavenGroup
+                ).isEqualTo(
+                    LibraryGroup("androidx.myGroup", atomicGroupVersion = null)
+                )
+                assertThat(
+                    extension.project.version
+                ).isEqualTo(Version("1.0.0-dev01"))
+                extension.validateMavenVersion()
+            },
+            validateWithoutKmp = { extension ->
+                extension.mavenMultiplatformVersion = Version("1.0.0-dev01")
+                extension.mavenVersion = Version("1.0.0")
+                assertThat(
+                    extension.mavenGroup
+                ).isEqualTo(
+                    LibraryGroup("androidx.myGroup", atomicGroupVersion = null)
+                )
+                assertThat(
+                    extension.project.version
+                ).isEqualTo(Version("1.0.0"))
+                extension.validateMavenVersion()
+            }
+        )
+    }
+
+    @Test
+    fun androidxExtension_withAtomicGroup() {
+        runAndroidExtensionTest(
+            projectPath = "myGroup:project1",
+            tomlFile = """
+                [versions]
+                V1 = "1.0.0"
+                V1_KMP = "1.0.0-dev01"
+                [groups]
+                G1 = { group = "androidx.myGroup", atomicGroupVersion = "versions.V1", multiplatformGroupVersion = "versions.V1_KMP" }
+            """.trimIndent(),
+            validateWithKmp = { extension ->
+                assertThat(
+                    extension.mavenGroup
+                ).isEqualTo(
+                    LibraryGroup("androidx.myGroup", atomicGroupVersion = Version("1.0.0-dev01"))
+                )
+                assertThat(
+                    extension.project.version
+                ).isEqualTo(Version("1.0.0-dev01"))
+                extension.validateMavenVersion()
+            },
+            validateWithoutKmp = { extension ->
+                assertThat(
+                    extension.mavenGroup
+                ).isEqualTo(
+                    LibraryGroup("androidx.myGroup", atomicGroupVersion = Version("1.0.0"))
+                )
+                assertThat(
+                    extension.project.version
+                ).isEqualTo(Version("1.0.0"))
+                extension.validateMavenVersion()
+            }
+        )
+    }
+
+    private fun runAndroidExtensionTest(
+        projectPath: String,
+        tomlFile: String,
+        validateWithoutKmp: (AndroidXExtension) -> Unit,
+        validateWithKmp: (AndroidXExtension) -> Unit
+    ) {
+        listOf(false, true).forEach { useKmpVersions ->
+            val rootProjectDir = tempDir.newFolder()
+            val rootProject = ProjectBuilder.builder().withProjectDir(
+                rootProjectDir
+            ).build()
+            val subject = ProjectBuilder.builder()
+                .withParent(rootProject)
+                .withName(projectPath)
+                .build()
+            // create the service before extensions are created so that they'll use the test service
+            // we've created.
+            createLibraryVersionsService(
+                tomlFile = tomlFile,
+                project = rootProject,
+                useMultiplatformGroupVersions = useKmpVersions
+            )
+            // needed for AndroidXExtension initialization
+            rootProject.setSupportRootFolder(rootProjectDir)
+            // create androidx extensions
+            val extension = subject.extensions
+                .create<AndroidXExtension>(AndroidXImplPlugin.EXTENSION_NAME)
+            if (useKmpVersions) {
+                validateWithKmp(extension)
+            } else {
+                validateWithoutKmp(extension)
+            }
+        }
+    }
+
     private fun createLibraryVersionsService(
         tomlFile: String,
         composeCustomVersion: String? = null,
         composeCustomGroup: String? = null,
-        useMultiplatformGroupVersions: Boolean = false
+        useMultiplatformGroupVersions: Boolean = false,
+        project: Project = ProjectBuilder.builder().withProjectDir(tempDir.newFolder()).build()
     ): LibraryVersionsService {
-        val project = ProjectBuilder.builder().withProjectDir(tempDir.newFolder()).build()
         val serviceProvider = project.gradle.sharedServices.registerIfAbsent(
             "libraryVersionsService", LibraryVersionsService::class.java
         ) { spec ->
diff --git a/buildSrc/jetpad-integration/build.gradle b/buildSrc/jetpad-integration/build.gradle
index 995d9c5..fc0c5cc 100644
--- a/buildSrc/jetpad-integration/build.gradle
+++ b/buildSrc/jetpad-integration/build.gradle
@@ -1 +1,6 @@
 apply plugin:"java"
+
+java {
+    sourceCompatibility = JavaVersion.VERSION_11
+    targetCompatibility = JavaVersion.VERSION_11
+}
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
index be0f9f3..bf6e0b1 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
@@ -18,7 +18,6 @@
 
 import androidx.build.checkapi.shouldConfigureApiTasks
 import androidx.build.transform.configureAarAsJarForConfiguration
-import com.android.build.gradle.internal.crash.afterEvaluate
 import groovy.lang.Closure
 import org.gradle.api.GradleException
 import org.gradle.api.Project
@@ -42,6 +41,8 @@
 
     val listProjectsService: Provider<ListProjectsService>
 
+    private val versionService: LibraryVersionsService
+
     init {
         val toml = lazyReadFile("libraryversions.toml")
 
@@ -52,21 +53,18 @@
         // `./gradlew :compose:compiler:compiler:publishToMavenLocal -Pandroidx.versionExtraCheckEnabled=false`
         val composeCustomVersion = project.providers.environmentVariable("COMPOSE_CUSTOM_VERSION")
         val composeCustomGroup = project.providers.environmentVariable("COMPOSE_CUSTOM_GROUP")
-        val useMultiplatformVersions = project.provider {
-            Multiplatform.isKotlinNativeEnabled(project)
-        }
-
         // service that can compute group/version for a project
-        val versionServiceProvider = project.gradle.sharedServices.registerIfAbsent(
+        versionService = project.gradle.sharedServices.registerIfAbsent(
             "libraryVersionsService",
             LibraryVersionsService::class.java
         ) { spec ->
             spec.parameters.tomlFile = toml
             spec.parameters.composeCustomVersion = composeCustomVersion
             spec.parameters.composeCustomGroup = composeCustomGroup
-            spec.parameters.useMultiplatformGroupVersions = useMultiplatformVersions
-        }
-        val versionService = versionServiceProvider.get()
+            spec.parameters.useMultiplatformGroupVersions = project.provider {
+                Multiplatform.isKotlinNativeEnabled(project)
+            }
+        }.get()
         AllLibraryGroups = versionService.libraryGroups.values.toList()
         LibraryVersions = versionService.libraryVersions
         libraryGroupsByGroupId = versionService.libraryGroupsByGroupId
@@ -87,11 +85,37 @@
 
     var name: Property<String?> = project.objects.property(String::class.java)
     fun setName(newName: String) { name.set(newName) }
+
+    /**
+     * Maven version of the library.
+     *
+     * Note that, setting this is an error if the library group sets an atomic version.
+     * If the build is a multiplatform build, this value will be overridden by
+     * the [mavenMultiplatformVersion] property when it is provided.
+     *
+     * @see mavenMultiplatformVersion
+     */
     var mavenVersion: Version? = null
         set(value) {
             field = value
             chooseProjectVersion()
         }
+        get() = if (versionService.useMultiplatformGroupVersions) {
+            mavenMultiplatformVersion ?: field
+        } else {
+            field
+        }
+
+    /**
+     * If set, this will override the [mavenVersion] property in multiplatform builds.
+     *
+     * @see mavenVersion
+     */
+    var mavenMultiplatformVersion: Version? = null
+        set(value) {
+            field = value
+            chooseProjectVersion()
+        }
 
     fun getAllProjectPathsInSameGroup(): List<String> {
         val allProjectPaths = listProjectsService.get().allPossibleProjectPaths
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index cea59f9..7b7a30e 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -29,6 +29,7 @@
 import androidx.build.checkapi.KmpApiTaskConfig
 import androidx.build.checkapi.LibraryApiTaskConfig
 import androidx.build.checkapi.configureProjectForApiTasks
+import androidx.build.dependencies.KOTLIN_VERSION
 import androidx.build.dependencyTracker.AffectedModuleDetector
 import androidx.build.docs.AndroidXKmpDocsImplPlugin
 import androidx.build.gradle.isRoot
@@ -125,6 +126,7 @@
         }
 
         project.configureKtlint()
+        project.configureKotlinStdlibVersion()
 
         // Configure all Jar-packing tasks for hermetic builds.
         project.tasks.withType(Zip::class.java).configureEach { it.configureForHermeticBuild() }
@@ -381,6 +383,20 @@
         }
     }
 
+    fun Project.configureKotlinStdlibVersion() {
+        project.configurations.all { configuration ->
+            configuration.resolutionStrategy { strategy ->
+                strategy.eachDependency { details ->
+                    if (details.requested.group == "org.jetbrains.kotlin" &&
+                        (details.requested.name == "kotlin-stdlib-jdk7" ||
+                            details.requested.name == "kotlin-stdlib-jdk8")) {
+                        details.useVersion(KOTLIN_VERSION)
+                    }
+                }
+            }
+        }
+    }
+
     @Suppress("UnstableApiUsage", "DEPRECATION") // AGP DSL APIs
     private fun configureWithLibraryPlugin(
         project: Project,
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt b/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
index 2885e3b..94bff7f 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
@@ -39,6 +39,9 @@
         Toml.parse(parameters.tomlFile.get())
     }
 
+    val useMultiplatformGroupVersions
+        get() = parameters.useMultiplatformGroupVersions.get()
+
     private fun getTable(key: String): TomlTable {
         return parsedTomlFile.getTable(key)
             ?: throw GradleException("Library versions toml file is missing [$key] table")
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt b/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
index 9a796c7e..b7664e3 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/MavenUploadHelper.kt
@@ -16,6 +16,7 @@
 
 package androidx.build
 
+import com.android.build.gradle.AppPlugin
 import com.android.build.gradle.LibraryPlugin
 import com.google.gson.GsonBuilder
 import com.google.gson.JsonObject
@@ -81,6 +82,21 @@
             }
         }
     }
+    // validate that all libraries that should be published actually get registered.
+    gradle.taskGraph.whenReady {
+        if (releaseTaskShouldBeRegistered(extension)) {
+            tasks.findByName(Release.PROJECT_ARCHIVE_ZIP_TASK_NAME)
+                ?: throw GradleException("Project $name is configured for publishing, but a " +
+                    "'createProjectZip' task was never registered. This is likely a bug in" +
+                    "AndroidX plugin configuration")
+        }
+    }
+}
+
+private fun Project.releaseTaskShouldBeRegistered(extension: AndroidXExtension): Boolean {
+    if (plugins.hasPlugin(AppPlugin::class.java)) { return false }
+    if (!extension.shouldRelease() && !isSnapshotBuild()) { return false }
+    return extension.shouldPublish()
 }
 
 /**
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
index 89d20ff..e126ddc 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
@@ -59,6 +59,7 @@
 import org.gradle.api.provider.ListProperty
 import org.gradle.api.provider.Provider
 import org.gradle.api.tasks.CacheableTask
+import org.gradle.api.tasks.Input
 import org.gradle.api.tasks.OutputDirectory
 import org.gradle.api.tasks.InputFiles
 import org.gradle.api.tasks.OutputFile
@@ -662,7 +663,7 @@
 @CacheableTask
 abstract class UnzipMultiplatformSourcesTask() : DefaultTask() {
 
-    @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE)
+    @get:Input
     abstract val inputJars: ListProperty<File>
 
     @OutputDirectory
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/dokka/kmpDocs/DokkaPartialDocsTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/dokka/kmpDocs/DokkaPartialDocsTask.kt
index d6f7f70..51dd0a1 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/dokka/kmpDocs/DokkaPartialDocsTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/dokka/kmpDocs/DokkaPartialDocsTask.kt
@@ -255,7 +255,7 @@
                             // This is a workaround for https://youtrack.jetbrains.com/issue/KT-33893
                             @Suppress("DEPRECATION") // for compatibility
                             (compilation.compileKotlinTask as
-                                org.jetbrains.kotlin.gradle.tasks.KotlinCompile).classpath
+                                org.jetbrains.kotlin.gradle.tasks.KotlinCompile).libraries
                         } else {
                             compilation.compileDependencyFiles
                         }
diff --git a/buildSrc/shared.gradle b/buildSrc/shared.gradle
index c14eb49..7ac3cae 100644
--- a/buildSrc/shared.gradle
+++ b/buildSrc/shared.gradle
@@ -54,6 +54,11 @@
     runtimeOnly(libs.wireGradlePluginz)
 }
 
+java {
+    sourceCompatibility = JavaVersion.VERSION_11
+    targetCompatibility = JavaVersion.VERSION_11
+}
+
 project.tasks.withType(Jar) { task ->
     task.reproducibleFileOrder = true
     task.preserveFileTimestamps = false
diff --git a/busytown/androidx_multiplatform_mac.sh b/busytown/androidx_multiplatform_mac.sh
index d7f9541..128e691 100755
--- a/busytown/androidx_multiplatform_mac.sh
+++ b/busytown/androidx_multiplatform_mac.sh
@@ -15,7 +15,7 @@
 # Setup simulators
 impl/androidx-native-mac-simulator-setup.sh
 
-impl/build.sh buildOnServer allTests :docs-kmp:zipCombinedKmpDocs --no-configuration-cache -Pandroidx.displayTestOutput=false
+impl/build.sh buildOnServer :docs-kmp:zipCombinedKmpDocs --no-configuration-cache -Pandroidx.displayTestOutput=false
 
 # run a separate createArchive task to prepare a repository
 # folder in DIST.
diff --git a/busytown/androidx_multiplatform_mac_host_tests.sh b/busytown/androidx_multiplatform_mac_host_tests.sh
new file mode 120000
index 0000000..aa12d53
--- /dev/null
+++ b/busytown/androidx_multiplatform_mac_host_tests.sh
@@ -0,0 +1 @@
+androidx_multiplatform_host_tests_mac.sh
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CameraControlAdapterDeviceTest.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CameraControlAdapterDeviceTest.kt
index 04b67f6..02bb86f 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CameraControlAdapterDeviceTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CameraControlAdapterDeviceTest.kt
@@ -18,11 +18,17 @@
 
 import android.content.Context
 import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraCharacteristics.CONTROL_MAX_REGIONS_AE
+import android.hardware.camera2.CameraCharacteristics.CONTROL_MAX_REGIONS_AF
+import android.hardware.camera2.CameraCharacteristics.CONTROL_MAX_REGIONS_AWB
 import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON
 import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH
 import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH
 import android.hardware.camera2.CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION
 import android.hardware.camera2.CaptureRequest.CONTROL_AE_MODE
+import android.hardware.camera2.CaptureRequest.CONTROL_AE_REGIONS
+import android.hardware.camera2.CaptureRequest.CONTROL_AF_REGIONS
+import android.hardware.camera2.CaptureRequest.CONTROL_AWB_REGIONS
 import android.hardware.camera2.CaptureRequest.CONTROL_CAPTURE_INTENT
 import android.hardware.camera2.CaptureRequest.CONTROL_CAPTURE_INTENT_CUSTOM
 import android.hardware.camera2.CaptureRequest.CONTROL_ZOOM_RATIO
@@ -30,6 +36,7 @@
 import android.hardware.camera2.CaptureRequest.FLASH_MODE_TORCH
 import android.hardware.camera2.CaptureRequest.SCALER_CROP_REGION
 import android.os.Build
+import androidx.camera.camera2.pipe.CameraGraph
 import androidx.camera.camera2.pipe.FrameInfo
 import androidx.camera.camera2.pipe.RequestMetadata
 import androidx.camera.camera2.pipe.integration.adapter.CameraControlAdapter
@@ -38,19 +45,21 @@
 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
 import androidx.camera.camera2.pipe.testing.VerifyResultListener
 import androidx.camera.core.CameraSelector
+import androidx.camera.core.FocusMeteringAction
 import androidx.camera.core.ImageAnalysis
 import androidx.camera.core.ImageCapture
+import androidx.camera.core.SurfaceOrientedMeteringPointFactory
 import androidx.camera.core.UseCase
 import androidx.camera.core.internal.CameraUseCaseAdapter
 import androidx.camera.testing.CameraUtil
 import androidx.camera.testing.CameraXUtil
-import androidx.camera.testing.LabTestRule
 import androidx.concurrent.futures.await
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth
+import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.runBlocking
@@ -61,10 +70,10 @@
 import org.junit.Assume
 import org.junit.Assume.assumeThat
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import java.util.concurrent.TimeUnit
 
 private val TIMEOUT = TimeUnit.SECONDS.toMillis(10)
 
@@ -92,10 +101,6 @@
     @get:Rule
     val useCamera = CameraUtil.grantCameraPermissionAndPreTest()
 
-    // TODO(b/187015621): Remove the rule after the surface can be safely closed.
-    @get:Rule
-    val labTest: LabTestRule = LabTestRule()
-
     @Before
     fun setUp() {
         Assume.assumeTrue(CameraUtil.hasCameraWithLensFacing(CameraSelector.LENS_FACING_BACK))
@@ -122,9 +127,11 @@
     }
 
     @After
-    fun tearDown() {
+    fun tearDown(): Unit = runBlocking {
         if (::camera.isInitialized) {
-            camera.detachUseCases()
+            withContext(Dispatchers.Main) {
+                camera.detachUseCases()
+            }
         }
 
         CameraXUtil.shutdown()[10000, TimeUnit.MILLISECONDS]
@@ -132,7 +139,6 @@
 
     // TODO: test all public API of the CameraControl to ensure the RequestOptions still exist
     //  after adding/removing the UseCase.
-    @LabTestRule.LabTestOnly
     @Test
     fun addUseCase_requestOptionsShouldSetToCamera(): Unit = runBlocking {
         // Arrange.
@@ -153,7 +159,7 @@
 
     // TODO: test all public API of the CameraControl to ensure the RequestOptions still exist
     //  after adding/removing the UseCase.
-    @LabTestRule.LabTestOnly
+    @Ignore("b/265316774")
     @Test
     fun removeUseCase_requestOptionsShouldSetToCamera(): Unit = runBlocking {
         // Arrange.
@@ -172,7 +178,6 @@
         verifyRequestOptions()
     }
 
-    @LabTestRule.LabTestOnly
     @Test
     fun setFlashModeAuto_aeModeSetAndRequestUpdated(): Unit = runBlocking {
         Assume.assumeTrue(hasFlashUnit)
@@ -188,7 +193,6 @@
         Truth.assertThat(cameraControl.flashMode).isEqualTo(ImageCapture.FLASH_MODE_AUTO)
     }
 
-    @LabTestRule.LabTestOnly
     @Test
     fun setFlashModeOff_aeModeSetAndRequestUpdated(): Unit = runBlocking {
         Assume.assumeTrue(hasFlashUnit)
@@ -204,7 +208,6 @@
         Truth.assertThat(cameraControl.flashMode).isEqualTo(ImageCapture.FLASH_MODE_OFF)
     }
 
-    @LabTestRule.LabTestOnly
     @Test
     fun setFlashModeOn_aeModeSetAndRequestUpdated(): Unit = runBlocking {
         Assume.assumeTrue(hasFlashUnit)
@@ -220,7 +223,6 @@
         Truth.assertThat(cameraControl.flashMode).isEqualTo(ImageCapture.FLASH_MODE_ON)
     }
 
-    @LabTestRule.LabTestOnly
     @Test
     fun enableTorch_aeModeSetAndRequestUpdated(): Unit = runBlocking {
         Assume.assumeTrue(hasFlashUnit)
@@ -236,7 +238,6 @@
         )
     }
 
-    @LabTestRule.LabTestOnly
     @Test
     fun disableTorchFlashModeAuto_aeModeSetAndRequestUpdated(): Unit = runBlocking {
         Assume.assumeTrue(hasFlashUnit)
@@ -253,6 +254,94 @@
         )
     }
 
+    @Test
+    fun startFocusAndMetering_3ARegionsUpdated() = runBlocking {
+        Assume.assumeTrue(
+            characteristics.getMaxRegionCount(CONTROL_MAX_REGIONS_AF) > 0 ||
+                characteristics.getMaxRegionCount(CONTROL_MAX_REGIONS_AE) > 0 ||
+                characteristics.getMaxRegionCount(CONTROL_MAX_REGIONS_AWB) > 0
+        )
+        val factory = SurfaceOrientedMeteringPointFactory(1.0f, 1.0f)
+        val action = FocusMeteringAction.Builder(factory.createPoint(0f, 0f)).build()
+        bindUseCase(imageAnalysis)
+
+        // Act.
+        cameraControl.startFocusAndMetering(action).await()
+
+        // Assert. Here we verify only 3A region count is correct.
+        val expectedAfCount =
+            characteristics.getMaxRegionCount(CONTROL_MAX_REGIONS_AF).coerceAtMost(1)
+        val expectedAeCount =
+            characteristics.getMaxRegionCount(CONTROL_MAX_REGIONS_AE).coerceAtMost(1)
+        val expectedAwbCount =
+            characteristics.getMaxRegionCount(CONTROL_MAX_REGIONS_AWB).coerceAtMost(1)
+        waitForResult(captureCount = 60).verify(
+            { requestMeta: RequestMetadata, _ ->
+                val afRegionMatched = requestMeta.getOrDefault(
+                    CONTROL_AF_REGIONS,
+                    emptyArray()
+                ).size == expectedAfCount
+
+                val aeRegionMatched = requestMeta.getOrDefault(
+                    CONTROL_AE_REGIONS,
+                    emptyArray()
+                ).size == expectedAeCount
+
+                val awbRegionMatched = requestMeta.getOrDefault(
+                    CONTROL_AWB_REGIONS,
+                    emptyArray()
+                ).size == expectedAwbCount
+
+                afRegionMatched && aeRegionMatched && awbRegionMatched
+            },
+            TIMEOUT
+        )
+    }
+
+    @Test
+    fun cancelFocusAndMetering_3ARegionsReset() = runBlocking {
+        Assume.assumeTrue(
+            characteristics.getMaxRegionCount(CONTROL_MAX_REGIONS_AF) > 0 ||
+                characteristics.getMaxRegionCount(CONTROL_MAX_REGIONS_AE) > 0 ||
+                characteristics.getMaxRegionCount(CONTROL_MAX_REGIONS_AWB) > 0
+        )
+        val factory = SurfaceOrientedMeteringPointFactory(1.0f, 1.0f)
+        val action = FocusMeteringAction.Builder(factory.createPoint(0f, 0f)).build()
+        bindUseCase(imageAnalysis)
+
+        // Act.
+        cameraControl.startFocusAndMetering(action).await()
+        cameraControl.cancelFocusAndMetering().await()
+
+        // Assert. The regions are reset to the default.
+        waitForResult(captureCount = 60).verify(
+            { requestMeta: RequestMetadata, _ ->
+
+                val isDefaultAfRegion = requestMeta.getOrDefault(
+                    CONTROL_AF_REGIONS,
+                    CameraGraph.Constants3A.METERING_REGIONS_DEFAULT
+                ).contentEquals(CameraGraph.Constants3A.METERING_REGIONS_DEFAULT)
+
+                val isDefaultAeRegion = requestMeta.getOrDefault(
+                    CONTROL_AE_REGIONS,
+                    CameraGraph.Constants3A.METERING_REGIONS_DEFAULT
+                ).contentEquals(CameraGraph.Constants3A.METERING_REGIONS_DEFAULT)
+
+                val isDefaultAwbRegion = requestMeta.getOrDefault(
+                    CONTROL_AWB_REGIONS,
+                    CameraGraph.Constants3A.METERING_REGIONS_DEFAULT
+                ).contentEquals(CameraGraph.Constants3A.METERING_REGIONS_DEFAULT)
+
+                isDefaultAfRegion && isDefaultAeRegion && isDefaultAwbRegion
+            },
+            TIMEOUT
+        )
+    }
+
+    private fun CameraCharacteristics.getMaxRegionCount(
+        option_max_regions: CameraCharacteristics.Key<Int>
+    ) = get(option_max_regions) ?: 0
+
     private suspend fun arrangeRequestOptions() {
         cameraControl.setExposureCompensationIndex(1)
         cameraControl.setZoomRatio(1.0f)
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CaptureConfigAdapterDeviceTest.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CaptureConfigAdapterDeviceTest.kt
index 7f95975..3f62b210 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CaptureConfigAdapterDeviceTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CaptureConfigAdapterDeviceTest.kt
@@ -36,7 +36,6 @@
 import androidx.camera.core.internal.CameraUseCaseAdapter
 import androidx.camera.testing.CameraUtil
 import androidx.camera.testing.CameraXUtil
-import androidx.camera.testing.LabTestRule
 import androidx.camera.testing.fakes.FakeUseCase
 import androidx.camera.testing.fakes.FakeUseCaseConfig
 import androidx.test.core.app.ApplicationProvider
@@ -70,10 +69,6 @@
     @get:Rule
     val useCamera: TestRule = CameraUtil.grantCameraPermissionAndPreTest()
 
-    // TODO(b/187015621): Remove the rule after the surface can be safely closed.
-    @get:Rule
-    val labTest: LabTestRule = LabTestRule()
-
     private var cameraControl: CameraControlAdapter? = null
     private var camera: CameraUseCaseAdapter? = null
     private lateinit var testDeferrableSurface: TestDeferrableSurface
@@ -119,7 +114,6 @@
     }
 
     @Test
-    @LabTestRule.LabTestOnly
     fun tagBundleTest() = runBlocking {
         // Arrange
         val deferred = CompletableDeferred<CameraCaptureResult>()
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/EvCompDeviceTest.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/EvCompDeviceTest.kt
index 9f8a0df..b1781dd 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/EvCompDeviceTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/EvCompDeviceTest.kt
@@ -32,13 +32,14 @@
 import androidx.camera.core.internal.CameraUseCaseAdapter
 import androidx.camera.testing.CameraUtil
 import androidx.camera.testing.CameraXUtil
-import androidx.camera.testing.LabTestRule
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
 import androidx.testutils.assertThrows
 import com.google.common.truth.Truth
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.runBlocking
@@ -48,8 +49,6 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import java.util.concurrent.TimeUnit
-import java.util.concurrent.TimeoutException
 
 @LargeTest
 @RunWith(AndroidJUnit4::class)
@@ -64,10 +63,6 @@
     @get:Rule
     val useCamera = CameraUtil.grantCameraPermissionAndPreTest()
 
-    // TODO(b/187015621): Remove the rule after the surface can be safely closed.
-    @get:Rule
-    val labTest: LabTestRule = LabTestRule()
-
     @Before
     fun setUp() {
         // TODO(b/162296654): Workaround the google_3a specific behavior.
@@ -112,7 +107,6 @@
     }
 
     @Test
-    @LabTestRule.LabTestOnly
     fun setExposure_futureResultTest() {
         val exposureState = camera.cameraInfo.exposureState
         Assume.assumeTrue(exposureState.isExposureCompensationSupported)
@@ -133,7 +127,6 @@
     }
 
     @Test
-    @LabTestRule.LabTestOnly
     fun setExposureTest() = runBlocking {
         val exposureState = camera.cameraInfo.exposureState
         Assume.assumeTrue(exposureState.isExposureCompensationSupported)
@@ -149,7 +142,6 @@
     }
 
     @Test
-    @LabTestRule.LabTestOnly
     fun setExposureTest_runTwice() = runBlocking {
         val exposureState = camera.cameraInfo.exposureState
         Assume.assumeTrue(exposureState.isExposureCompensationSupported)
@@ -169,7 +161,6 @@
     }
 
     @Test
-    @LabTestRule.LabTestOnly
     fun setExposureAndZoomRatio_theExposureSettingShouldApply() = runBlocking {
         val exposureState = camera.cameraInfo.exposureState
         Assume.assumeTrue(exposureState.isExposureCompensationSupported)
@@ -192,7 +183,6 @@
     }
 
     @Test
-    @LabTestRule.LabTestOnly
     fun setExposureAndLinearZoom_theExposureSettingShouldApply() = runBlocking {
         val exposureState = camera.cameraInfo.exposureState
         Assume.assumeTrue(exposureState.isExposureCompensationSupported)
@@ -210,7 +200,6 @@
     }
 
     @Test
-    @LabTestRule.LabTestOnly
     fun setExposureAndFlash_theExposureSettingShouldApply() = runBlocking {
         val exposureState = camera.cameraInfo.exposureState
         Assume.assumeTrue(exposureState.isExposureCompensationSupported)
@@ -228,7 +217,6 @@
     }
 
     @Test
-    @LabTestRule.LabTestOnly
     fun setExposureTimeout_theNextCallShouldWork() = runBlocking {
         val exposureState = camera.cameraInfo.exposureState
         Assume.assumeTrue(exposureState.isExposureCompensationSupported)
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt
index 185bcad..d19ee81 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraControlAdapter.kt
@@ -105,9 +105,8 @@
 
     override fun startFocusAndMetering(
         action: FocusMeteringAction
-    ): ListenableFuture<FocusMeteringResult> {
-        return focusMeteringControl.startFocusAndMetering(action)
-    }
+    ): ListenableFuture<FocusMeteringResult> =
+        Futures.nonCancellationPropagating(focusMeteringControl.startFocusAndMetering(action))
 
     override fun cancelFocusAndMetering(): ListenableFuture<Void> {
         return Futures.nonCancellationPropagating(
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FocusMeteringControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FocusMeteringControl.kt
index 8f85b20..23f0b97 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FocusMeteringControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FocusMeteringControl.kt
@@ -27,7 +27,7 @@
 import androidx.camera.camera2.pipe.Result3A
 import androidx.camera.camera2.pipe.integration.adapter.asListenableFuture
 import androidx.camera.camera2.pipe.integration.config.CameraScope
-import androidx.camera.core.CameraControl
+import androidx.camera.core.CameraControl.OperationCanceledException
 import androidx.camera.core.FocusMeteringAction
 import androidx.camera.core.FocusMeteringResult
 import androidx.camera.core.MeteringPoint
@@ -41,6 +41,7 @@
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withTimeoutOrNull
 
 /**
  * Implementation of focus and metering controls exposed by [CameraControlInternal].
@@ -89,41 +90,85 @@
         cameraProperties.metadata.getOrDefault(CameraCharacteristics.CONTROL_MAX_REGIONS_AE, 0)
     private val maxAwbRegionCount =
         cameraProperties.metadata.getOrDefault(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB, 0)
+    private var updateSignal: CompletableDeferred<FocusMeteringResult>? = null
+    private var cancelSignal: CompletableDeferred<Result3A?>? = null
 
     fun startFocusAndMetering(
-        action: FocusMeteringAction
+        action: FocusMeteringAction,
+        autoFocusTimeoutMs: Long = AUTO_FOCUS_TIMEOUT_DURATION,
     ): ListenableFuture<FocusMeteringResult> {
         val signal = CompletableDeferred<FocusMeteringResult>()
 
         useCaseCamera?.let { useCaseCamera ->
-            threads.sequentialScope.launch {
-                signal.complete(
+            val job = threads.sequentialScope.launch {
+                cancelSignal?.setCancelException("Cancelled by another startFocusAndMetering()")
+                updateSignal?.setCancelException("Cancelled by another startFocusAndMetering()")
+                updateSignal = signal
+
+                val aeRectangles = meteringRegionsFromMeteringPoints(
+                    action.meteringPointsAe,
+                    maxAeRegionCount,
+                    sensorRect,
+                    defaultAspectRatio
+                )
+                val afRectangles = meteringRegionsFromMeteringPoints(
+                    action.meteringPointsAf,
+                    maxAfRegionCount,
+                    sensorRect,
+                    defaultAspectRatio
+                )
+                val awbRectangles = meteringRegionsFromMeteringPoints(
+                    action.meteringPointsAwb,
+                    maxAwbRegionCount,
+                    sensorRect,
+                    defaultAspectRatio
+                )
+                if (aeRectangles.isEmpty() && afRectangles.isEmpty() && awbRectangles.isEmpty()) {
+                    signal.completeExceptionally(
+                        IllegalArgumentException(
+                            "None of the specified AF/AE/AWB MeteringPoints is supported on" +
+                                " this camera."
+                        )
+                    )
+                    return@launch
+                }
+                val (isCancelEnabled, timeout) = if (action.isAutoCancelEnabled &&
+                    action.autoCancelDurationInMillis < autoFocusTimeoutMs
+                ) {
+                    (true to action.autoCancelDurationInMillis)
+                } else {
+                    (false to autoFocusTimeoutMs)
+                }
+                withTimeoutOrNull(timeout) {
                     useCaseCamera.requestControl.startFocusAndMeteringAsync(
-                        aeRegions = meteringRegionsFromMeteringPoints(
-                            action.meteringPointsAe,
-                            maxAeRegionCount,
-                            sensorRect,
-                            defaultAspectRatio
-                        ),
-                        afRegions = meteringRegionsFromMeteringPoints(
-                            action.meteringPointsAf,
-                            maxAfRegionCount,
-                            sensorRect,
-                            defaultAspectRatio
-                        ),
-                        awbRegions = meteringRegionsFromMeteringPoints(
-                            action.meteringPointsAwb,
-                            maxAwbRegionCount,
-                            sensorRect,
-                            defaultAspectRatio
-                        ),
+                        aeRegions = aeRectangles,
+                        afRegions = afRectangles,
+                        awbRegions = awbRectangles,
                         afTriggerStartAeMode = cameraProperties.getSupportedAeMode(AeMode.ON)
                     ).await().toFocusMeteringResult(true)
-                )
+                }.let { focusMeteringResult ->
+                    if (focusMeteringResult != null) {
+                        signal.complete(focusMeteringResult)
+                    } else {
+                        if (isCancelEnabled) {
+                            if (signal.isActive) {
+                                cancelFocusAndMeteringNow(useCaseCamera, signal)
+                            }
+                        } else {
+                            signal.complete(FocusMeteringResult.create(false))
+                        }
+                    }
+                }
+            }
+
+            signal.invokeOnCompletion { throwable ->
+                if (throwable is OperationCanceledException) {
+                    job.cancel()
+                }
             }
         } ?: run {
             signal.completeExceptionally(
-                CameraControl.OperationCanceledException("Camera is not active.")
+                OperationCanceledException("Camera is not active.")
             )
         }
 
@@ -171,8 +216,31 @@
         } else AeMode.OFF
     }
 
-    fun cancelFocusAndMeteringAsync(): Deferred<Unit> {
-        return CompletableDeferred(null)
+    fun cancelFocusAndMeteringAsync(): Deferred<Result3A?> {
+        val signal = CompletableDeferred<Result3A?>()
+        useCaseCamera?.let { useCaseCamera ->
+            threads.sequentialScope.launch {
+                cancelSignal?.setCancelException("Cancelled by another cancelFocusAndMetering()")
+                cancelSignal = signal
+                signal.complete(cancelFocusAndMeteringNow(useCaseCamera, updateSignal))
+            }
+        } ?: run {
+            signal.completeExceptionally(OperationCanceledException("Camera is not active."))
+        }
+
+        return signal
+    }
+
+    private suspend fun cancelFocusAndMeteringNow(
+        useCaseCamera: UseCaseCamera,
+        signalToCancel: CompletableDeferred<FocusMeteringResult>?,
+    ): Result3A {
+        signalToCancel?.setCancelException("Cancelled by cancelFocusAndMetering()")
+        return useCaseCamera.requestControl.cancelFocusAndMeteringAsync().await()
+    }
+
+    private fun <T> CompletableDeferred<T>.setCancelException(message: String) {
+        completeExceptionally(OperationCanceledException(message))
     }
 
     /**
@@ -194,6 +262,7 @@
 
     companion object {
         const val METERING_WEIGHT_DEFAULT = MeteringRectangle.METERING_WEIGHT_MAX
+        const val AUTO_FOCUS_TIMEOUT_DURATION = 5000L
 
         fun meteringRegionsFromMeteringPoints(
             meteringPoints: List<MeteringPoint>,
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
index 86749f7..cfb3151 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
@@ -71,6 +71,7 @@
 /**
  * API for interacting with a [CameraGraph] that has been configured with a set of [UseCase]'s
  */
+@RequiresApi(21)
 @UseCaseCameraScope
 class UseCaseCameraImpl @Inject constructor(
     private val useCaseGraphConfig: UseCaseGraphConfig,
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
index 8410aed..3d9377d 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
@@ -26,13 +26,13 @@
 import androidx.camera.camera2.pipe.AeMode
 import androidx.camera.camera2.pipe.AfMode
 import androidx.camera.camera2.pipe.AwbMode
+import androidx.camera.camera2.pipe.CameraGraph.Constants3A.METERING_REGIONS_DEFAULT
 import androidx.camera.camera2.pipe.Lock3ABehavior
 import androidx.camera.camera2.pipe.Request
 import androidx.camera.camera2.pipe.RequestTemplate
 import androidx.camera.camera2.pipe.Result3A
 import androidx.camera.camera2.pipe.StreamId
 import androidx.camera.camera2.pipe.TorchState
-import androidx.camera.camera2.pipe.core.Log
 import androidx.camera.camera2.pipe.integration.adapter.CaptureConfigAdapter
 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraScope
 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
@@ -45,7 +45,6 @@
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
-import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.async
 
@@ -291,9 +290,17 @@
     }
 
     override suspend fun cancelFocusAndMeteringAsync(): Deferred<Result3A> {
-        // TODO(b/205662153): Implement to cancel FocusAndMetering.
-        Log.warn { "TODO: cancelFocusAndMetering is not yet supported" }
-        return CompletableDeferred(Result3A(Result3A.Status.OK))
+        graph.acquireSession().use {
+            it.unlock3A(ae = true, af = true, awb = true)
+        }.await()
+
+        return graph.acquireSession().use {
+            it.update3A(
+                aeRegions = METERING_REGIONS_DEFAULT.asList(),
+                afRegions = METERING_REGIONS_DEFAULT.asList(),
+                awbRegions = METERING_REGIONS_DEFAULT.asList()
+            )
+        }
     }
 
     override suspend fun issueSingleCaptureAsync(
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
index e49ea46..f57b581 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManager.kt
@@ -31,6 +31,8 @@
 import androidx.camera.core.UseCase
 import androidx.camera.core.impl.DeferrableSurface
 import androidx.camera.core.impl.SessionConfig.ValidatingBuilder
+import androidx.camera.core.impl.utils.Threads
+import androidx.lifecycle.MutableLiveData
 import javax.inject.Inject
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.joinAll
@@ -218,7 +220,7 @@
         when {
             shouldAddRepeatingUseCase(runningUseCases) -> addRepeatingUseCase()
             shouldRemoveRepeatingUseCase(runningUseCases) -> removeRepeatingUseCase()
-            else -> camera?.runningUseCasesLiveData?.value = runningUseCases
+            else -> camera?.runningUseCasesLiveData?.setLiveDataValue(runningUseCases)
         }
     }
 
@@ -324,4 +326,12 @@
         val captureConfig = sessionConfig.repeatingCaptureConfig
         return predicate(captureConfig.surfaces, sessionConfig.surfaces)
     }
+
+    private fun <T> MutableLiveData<T>.setLiveDataValue(value: T?) {
+        if (Threads.isMainThread()) {
+            this.value = value
+        } else {
+            this.postValue(value)
+        }
+    }
 }
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/FocusMeteringControlTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/FocusMeteringControlTest.kt
index e970d28c..50afb50 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/FocusMeteringControlTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/FocusMeteringControlTest.kt
@@ -50,6 +50,7 @@
 import androidx.camera.testing.fakes.FakeCamera
 import androidx.camera.testing.fakes.FakeUseCase
 import androidx.lifecycle.MutableLiveData
+import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import com.google.common.util.concurrent.FutureCallback
@@ -59,12 +60,14 @@
 import java.util.concurrent.ExecutionException
 import java.util.concurrent.Executors
 import java.util.concurrent.TimeUnit
-import junit.framework.TestCase
+import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
 import org.junit.Assert.assertThrows
@@ -128,7 +131,8 @@
     @Before
     fun setUp() {
         loadCameraProperties()
-        fakeRequestControl.focusMeteringResult3A = Result3A(status = Result3A.Status.OK)
+        fakeRequestControl.focusMeteringResult =
+            CompletableDeferred(Result3A(status = Result3A.Status.OK))
         focusMeteringControl = initFocusMeteringControl(CAMERA_ID_0)
     }
 
@@ -279,7 +283,9 @@
         // TODO: This will probably throw an invalid argument exception in future instead of
         //  passing the parameters to request control, better to assert the exception then.
 
-        with(fakeRequestControl.focusMeteringCalls.last()) {
+        val meteringRequests = fakeRequestControl.focusMeteringCalls.lastOrNull()
+            ?: FakeUseCaseCameraRequestControl.FocusMeteringParams()
+        with(meteringRequests) {
             assertWithMessage("Wrong number of AE regions").that(aeRegions.size).isEqualTo(0)
             assertWithMessage("Wrong number of AF regions").that(afRegions.size).isEqualTo(0)
             assertWithMessage("Wrong number of AWB regions").that(awbRegions.size).isEqualTo(0)
@@ -498,11 +504,13 @@
 
     @Test
     fun startFocusMetering_AfLocked_completesWithFocusFalse() {
-        fakeRequestControl.focusMeteringResult3A = Result3A(
-            status = Result3A.Status.OK,
-            frameMetadata = FakeFrameMetadata(
-                extraMetadata = mapOf(
-                    CONTROL_AF_STATE to CONTROL_AF_STATE_FOCUSED_LOCKED
+        fakeRequestControl.focusMeteringResult = CompletableDeferred(
+            Result3A(
+                status = Result3A.Status.OK,
+                frameMetadata = FakeFrameMetadata(
+                    extraMetadata = mapOf(
+                        CONTROL_AF_STATE to CONTROL_AF_STATE_FOCUSED_LOCKED
+                    )
                 )
             )
         )
@@ -515,11 +523,13 @@
 
     @Test
     fun startFocusMetering_AfNotLocked_completesWithFocusFalse() {
-        fakeRequestControl.focusMeteringResult3A = Result3A(
-            status = Result3A.Status.OK,
-            frameMetadata = FakeFrameMetadata(
-                extraMetadata = mapOf(
-                    CONTROL_AF_STATE to CONTROL_AF_STATE_NOT_FOCUSED_LOCKED
+        fakeRequestControl.focusMeteringResult = CompletableDeferred(
+            Result3A(
+                status = Result3A.Status.OK,
+                frameMetadata = FakeFrameMetadata(
+                    extraMetadata = mapOf(
+                        CONTROL_AF_STATE to CONTROL_AF_STATE_NOT_FOCUSED_LOCKED
+                    )
                 )
             )
         )
@@ -533,11 +543,13 @@
     @Test
     @Ignore("b/263323720: When AfState is null, it means AF is not supported")
     fun startFocusMetering_AfStateIsNull_completesWithFocusTrue() {
-        fakeRequestControl.focusMeteringResult3A = Result3A(
-            status = Result3A.Status.OK,
-            frameMetadata = FakeFrameMetadata(
-                extraMetadata = mapOf(
-                    CONTROL_AF_STATE to null
+        fakeRequestControl.focusMeteringResult = CompletableDeferred(
+            Result3A(
+                status = Result3A.Status.OK,
+                frameMetadata = FakeFrameMetadata(
+                    extraMetadata = mapOf(
+                        CONTROL_AF_STATE to null
+                    )
                 )
             )
         )
@@ -566,50 +578,73 @@
         assertFutureFocusCompleted(result, true)
     }
 
+    @MediumTest
     @Test
-    @Ignore("b/205662153")
-    fun startFocusMetering_cancelledBeforeCompletion_failsWithOperationCanceledOperation() {
+    fun startFocusMetering_cancelledBeforeCompletion_failsWithOperationCanceledOperation() =
+        runBlocking {
+        // Arrange. Set a delay CompletableDeferred
+        fakeRequestControl.focusMeteringResult = CompletableDeferred<Result3A>().apply {
+            async(Dispatchers.Default) {
+                delay(500)
+                complete(
+                    Result3A(
+                        status = Result3A.Status.OK,
+                        frameMetadata = FakeFrameMetadata(
+                            extraMetadata = mapOf(
+                                CONTROL_AF_STATE to CONTROL_AF_STATE_FOCUSED_LOCKED
+                            )
+                        )
+                    )
+                )
+            }
+        }
         val action = FocusMeteringAction.Builder(point1).build()
-        val future = focusMeteringControl.startFocusAndMetering(
-            action
-        )
+        val future = focusMeteringControl.startFocusAndMetering(action)
 
-        // TODO: Check if the following is the correct method to call while enabling this test
+        // Act.
         focusMeteringControl.cancelFocusAndMeteringAsync()
 
-        try {
-            future.get()
-            TestCase.fail("The future should fail.")
-        } catch (e: ExecutionException) {
-            assertThat(e.cause)
-                .isInstanceOf(CameraControl.OperationCanceledException::class.java)
-        } catch (e: InterruptedException) {
-            assertThat(e.cause)
-                .isInstanceOf(CameraControl.OperationCanceledException::class.java)
-        }
+        // Assert.
+        assertFutureFailedWithOperationCancellation(future)
     }
 
     @Test
-    @Ignore("b/205662153: Enable when cancelFocusAndMetering implementation is completed")
     fun startThenCancelThenStart_previous2FuturesFailsWithOperationCanceled() {
-        val action = FocusMeteringAction.Builder(point1)
-            .build()
+        // Arrange. Set a never complete CompletableDeferred
+        fakeRequestControl.focusMeteringResult = CompletableDeferred()
+        fakeRequestControl.cancelFocusMeteringResult = CompletableDeferred()
+        val action = FocusMeteringAction.Builder(point1).build()
 
+        // Act.
         val result1 = focusMeteringControl.startFocusAndMetering(action)
-        // TODO: b/205662153
-//        val result2 = focusMeteringControl.cancelFocusAndMetering()
+        val result2 = focusMeteringControl.cancelFocusAndMeteringAsync().asListenableFuture()
         focusMeteringControl.startFocusAndMetering(action)
 
+        // Assert.
         assertFutureFailedWithOperationCancellation(result1)
-        // TODO: b/205662153
-//        assertFutureFailedWithOperationCancellation(result2)
+        assertFutureFailedWithOperationCancellation(result2)
     }
 
+    @MediumTest
     @Test
-    @Ignore("b/205662153: Enable when cancelFocusAndMetering implementation is completed")
-    fun startMultipleActions_allExceptLatestAreCancelled() {
-        val action = FocusMeteringAction.Builder(point1)
-            .build()
+    fun startMultipleActions_allExceptLatestAreCancelled() = runBlocking {
+        // Arrange. Set a delay CompletableDeferred
+        fakeRequestControl.focusMeteringResult = CompletableDeferred<Result3A>().apply {
+            async(Dispatchers.Default) {
+                delay(500)
+                complete(
+                    Result3A(
+                        status = Result3A.Status.OK,
+                        frameMetadata = FakeFrameMetadata(
+                            extraMetadata = mapOf(
+                                CONTROL_AF_STATE to CONTROL_AF_STATE_FOCUSED_LOCKED
+                            )
+                        )
+                    )
+                )
+            }
+        }
+        val action = FocusMeteringAction.Builder(point1).build()
         val result1 = focusMeteringControl.startFocusAndMetering(action)
         val result2 = focusMeteringControl.startFocusAndMetering(action)
         val result3 = focusMeteringControl.startFocusAndMetering(action)
@@ -619,28 +654,32 @@
     }
 
     @Test
-    @Ignore("b/205662153: Enable when cancelFocusAndMetering implementation is completed")
-    fun startFocusMetering_focusedThenCancel_futureStillCompletes() {
-        fakeRequestControl.focusMeteringResult3A = Result3A(
-            status = Result3A.Status.OK,
-            frameMetadata = FakeFrameMetadata(
-                extraMetadata = mapOf(
-                    CONTROL_AF_STATE to CONTROL_AF_STATE_FOCUSED_LOCKED
+    fun startFocusMetering_focusedThenCancel_futureStillCompletes() = runBlocking {
+        // Arrange.
+        fakeRequestControl.focusMeteringResult = CompletableDeferred(
+            Result3A(
+                status = Result3A.Status.OK,
+                frameMetadata = FakeFrameMetadata(
+                    extraMetadata = mapOf(
+                        CONTROL_AF_STATE to CONTROL_AF_STATE_FOCUSED_LOCKED
+                    )
                 )
             )
         )
         val action = FocusMeteringAction.Builder(point1).build()
 
-        val result = focusMeteringControl.startFocusAndMetering(action)
+        val result = focusMeteringControl.startFocusAndMetering(action).apply {
+           get(3, TimeUnit.SECONDS)
+        }
 
-        // cancel it and then ensure the returned ListenableFuture still completes;
-        // TODO: Check if the following is the correct method to call while enabling this test
-        focusMeteringControl.cancelFocusAndMeteringAsync()
+        // Act. Cancel it and then ensure the returned ListenableFuture still completes.
+        focusMeteringControl.cancelFocusAndMeteringAsync().join()
+
+        // Assert.
         assertFutureFocusCompleted(result, true)
     }
 
     @Test
-    @Ignore("aosp/2369189")
     fun startFocusMeteringAFAEAWB_noPointsAreSupported_failFuture() {
         val focusMeteringControl = initFocusMeteringControl(CAMERA_ID_3)
         val action = FocusMeteringAction.Builder(
@@ -658,7 +697,6 @@
     }
 
     @Test
-    @Ignore("aosp/2369189")
     fun startFocusMeteringAEAWB_noPointsAreSupported_failFuture() {
         val focusMeteringControl = initFocusMeteringControl(CAMERA_ID_3)
         val action = FocusMeteringAction.Builder(
@@ -675,7 +713,6 @@
     }
 
     @Test
-    @Ignore("aosp/2369189")
     fun startFocusMeteringAFAWB_noPointsAreSupported_failFuture() {
         val focusMeteringControl = initFocusMeteringControl(CAMERA_ID_3)
         val action = FocusMeteringAction.Builder(
@@ -708,7 +745,6 @@
     }
 
     @Test
-    @Ignore("aosp/2369189")
     fun startFocusMetering_noPointsAreValid_failFuture() {
         val focusMeteringControl = initFocusMeteringControl(CAMERA_ID_0)
 
@@ -775,18 +811,130 @@
         assertThat(focusMeteringControl.isFocusMeteringSupported(action)).isFalse()
     }
 
+    @Test
+    fun cancelFocusMetering_actionIsCanceledAndFutureCompletes() {
+        // Arrange. Set a never complete CompletableDeferred
+        fakeRequestControl.focusMeteringResult = CompletableDeferred()
+        val action = FocusMeteringAction.Builder(point1).build()
+
+        // Act.
+        val actionResult = focusMeteringControl.startFocusAndMetering(action)
+        val cancelResult = focusMeteringControl.cancelFocusAndMeteringAsync().asListenableFuture()
+
+        // Assert.
+        assertFutureFailedWithOperationCancellation(actionResult)
+        assertThat(cancelResult[3, TimeUnit.SECONDS]?.status).isEqualTo(Result3A.Status.OK)
+    }
+
+    @MediumTest
+    @Test
+    fun cancelFocusAndMetering_autoCancelIsDisabled(): Unit = runBlocking {
+        // Arrange. Set a never complete CompletableDeferred
+        fakeRequestControl.focusMeteringResult = CompletableDeferred()
+        val autoCancelDuration: Long = 500
+        val action = FocusMeteringAction.Builder(point1)
+            .setAutoCancelDuration(autoCancelDuration, TimeUnit.MILLISECONDS)
+            .build()
+        val autoFocusTimeoutDuration: Long = 1000
+        focusMeteringControl.startFocusAndMetering(action, autoFocusTimeoutDuration)
+
+        // Act. Call cancel before the auto cancel occur.
+        focusMeteringControl.cancelFocusAndMeteringAsync().await()
+        assertThat(fakeRequestControl.cancelFocusMeteringCallCount).isEqualTo(1)
+
+        // Assert. cancelFocusMetering only be invoked once.
+        delay(autoFocusTimeoutDuration)
+        assertThat(fakeRequestControl.cancelFocusMeteringCallCount).isEqualTo(1)
+    }
+
+    @MediumTest
+    @Test
+    fun autoCancelDuration_completeWithIsFocusSuccessfulFalse() {
+        // Arrange. Set a never complete CompletableDeferred
+        fakeRequestControl.focusMeteringResult = CompletableDeferred()
+        val autoCancelTimeOutDuration: Long = 500
+        val action = FocusMeteringAction.Builder(point1)
+            .setAutoCancelDuration(autoCancelTimeOutDuration, TimeUnit.MILLISECONDS)
+            .build()
+
+        // Act.
+        val future = focusMeteringControl.startFocusAndMetering(
+            action,
+            autoCancelTimeOutDuration
+        )
+
+        // Assert.
+        assertFutureFocusCompleted(future, false)
+    }
+
+    @MediumTest
+    @Test
+    fun shorterAutoCancelDuration_cancelIsCalled_completeActionFutureIsNotCalled(): Unit =
+        runBlocking {
+            // Arrange. Set a never complete CompletableDeferred
+            fakeRequestControl.focusMeteringResult = CompletableDeferred()
+            val autoCancelDuration: Long = 500
+            val action = FocusMeteringAction.Builder(point1)
+                .setAutoCancelDuration(autoCancelDuration, TimeUnit.MILLISECONDS)
+                .build()
+            val autoFocusTimeoutDuration: Long = 1000
+            focusMeteringControl.startFocusAndMetering(action, autoFocusTimeoutDuration)
+
+            // Act.
+            val future = focusMeteringControl.startFocusAndMetering(
+                action,
+                autoFocusTimeoutDuration
+            )
+
+            // Assert.
+            assertFutureFailedWithOperationCancellation(future)
+        }
+
+    @MediumTest
+    @Test
+    fun longerAutoCancelDuration_completeWithIsFocusSuccessfulFalse() {
+        // Arrange. Set a never complete CompletableDeferred
+        fakeRequestControl.focusMeteringResult = CompletableDeferred()
+        val autoCancelDuration: Long = 1000
+        val action = FocusMeteringAction.Builder(point1)
+            .setAutoCancelDuration(autoCancelDuration, TimeUnit.MILLISECONDS)
+            .build()
+        val autoFocusTimeoutDuration: Long = 500
+
+        // Act.
+        val future = focusMeteringControl.startFocusAndMetering(action, autoFocusTimeoutDuration)
+
+        // Assert.
+        assertFutureFocusCompleted(future, false)
+    }
+
+    @MediumTest
+    @Test
+    fun autoCancelDurationDisabled_completeAfterAutoFocusTimeoutDuration(): Unit = runBlocking {
+        // Arrange. Set a never complete CompletableDeferred
+        fakeRequestControl.focusMeteringResult = CompletableDeferred()
+        val autoCancelDuration: Long = 500
+        val action = FocusMeteringAction.Builder(point1)
+            .setAutoCancelDuration(autoCancelDuration, TimeUnit.MILLISECONDS)
+            .disableAutoCancel()
+            .build()
+        val autoFocusTimeoutTestDuration: Long = 1000
+
+        // Act.
+        val future = focusMeteringControl.startFocusAndMetering(
+            action, autoFocusTimeoutTestDuration
+        )
+
+        // Assert.
+        assertFutureFocusCompleted(future, false)
+    }
+
     // TODO: Port the following tests once their corresponding logics have been implemented.
     //  - [b/255679866] triggerAfWithTemplate, triggerAePrecaptureWithTemplate,
     //          cancelAfAeTriggerWithTemplate
     //  - startFocusAndMetering_AfRegionCorrectedByQuirk
     //  - [b/262225455] cropRegionIsSet_resultBasedOnCropRegion
-    //  - [b/205662153] autoCancelDuration_completeWithIsFocusSuccessfulFalse,
-    //      shorterAutoCancelDuration_cancelIsCalled_completeActionFutureIsNotCalled,
-    //      longerAutoCancelDuration_cancelIsCalled_afterCompleteWithIsFocusSuccessfulFalse,
-    //      autoCancelDurationDisabled_completeAfterAutoFocusTimeoutDuration
     //  The following ones will depend on how exactly they will be implemented.
-    //  - [b/205662153] cancelFocusAndMetering_* (probably many of these tests will no longer be
-    //      applicable in this level since Controller3A handles things a bit differently)
     //  - [b/264018162] addFocusMeteringOptions_hasCorrectAfMode,
     //                  startFocusMetering_isAfAutoModeIsTrue,
     //                  startFocusMetering_AfNotInvolved_isAfAutoModeIsSet,
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
index d318cbe..32977b3 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
@@ -92,7 +92,9 @@
     }
 
     val focusMeteringCalls = mutableListOf<FocusMeteringParams>()
-    var focusMeteringResult3A = Result3A(status = Result3A.Status.OK)
+    var focusMeteringResult = CompletableDeferred(Result3A(status = Result3A.Status.OK))
+    var cancelFocusMeteringCallCount = 0
+    var cancelFocusMeteringResult = CompletableDeferred(Result3A(status = Result3A.Status.OK))
 
     override suspend fun startFocusAndMeteringAsync(
         aeRegions: List<MeteringRectangle>,
@@ -103,11 +105,12 @@
         focusMeteringCalls.add(
             FocusMeteringParams(aeRegions, afRegions, awbRegions, afTriggerStartAeMode)
         )
-        return CompletableDeferred(focusMeteringResult3A)
+        return focusMeteringResult
     }
 
     override suspend fun cancelFocusAndMeteringAsync(): Deferred<Result3A> {
-        return CompletableDeferred(Result3A(status = Result3A.Status.OK))
+        cancelFocusMeteringCallCount++
+        return cancelFocusMeteringResult
     }
 
     override suspend fun issueSingleCaptureAsync(
@@ -120,10 +123,10 @@
     }
 
     data class FocusMeteringParams(
-        val aeRegions: List<MeteringRectangle>,
-        val afRegions: List<MeteringRectangle>,
-        val awbRegions: List<MeteringRectangle>,
-        val afTriggerStartAeMode: AeMode?
+        val aeRegions: List<MeteringRectangle> = emptyList(),
+        val afRegions: List<MeteringRectangle> = emptyList(),
+        val awbRegions: List<MeteringRectangle> = emptyList(),
+        val afTriggerStartAeMode: AeMode? = null
     )
 }
 
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
index 0c7138c..0f7f751 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraGraph.kt
@@ -26,7 +26,6 @@
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.CameraGraph.Constants3A.DEFAULT_FRAME_LIMIT
 import androidx.camera.camera2.pipe.CameraGraph.Constants3A.DEFAULT_TIME_LIMIT_NS
-import java.io.Closeable
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.flow.StateFlow
 
@@ -66,7 +65,7 @@
  * A [CameraGraph] represents the combined configuration and state of a camera.
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-public interface CameraGraph : Closeable {
+public interface CameraGraph : AutoCloseable {
     public val streams: StreamGraph
 
     /**
@@ -213,7 +212,7 @@
      * While this object is thread-safe, it should not shared or held for long periods of time.
      * Example: A [Session] should *not* be held during video recording.
      */
-    public interface Session : Closeable {
+    public interface Session : AutoCloseable {
         /**
          * Causes the CameraGraph to start or update the current repeating request with the
          * provided [Request] object. The [Request] object may be cached, and may be used for
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraSurfaceManager.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraSurfaceManager.kt
index b48552c..b6eac8d 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraSurfaceManager.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/CameraSurfaceManager.kt
@@ -22,7 +22,6 @@
 import androidx.camera.camera2.pipe.CameraSurfaceManager.SurfaceListener
 import androidx.camera.camera2.pipe.CameraSurfaceManager.SurfaceToken
 import androidx.camera.camera2.pipe.core.Log
-import java.io.Closeable
 import javax.inject.Inject
 import javax.inject.Singleton
 import kotlinx.atomicfu.atomic
@@ -63,7 +62,7 @@
      */
     inner class SurfaceToken(
         internal val surface: Surface
-    ) : Closeable {
+    ) : AutoCloseable {
         private val closed = atomic(false)
         override fun close() {
             if (closed.compareAndSet(expect = false, update = true)) {
@@ -118,7 +117,7 @@
         }
     }
 
-    internal fun registerSurface(surface: Surface): Closeable {
+    internal fun registerSurface(surface: Surface): AutoCloseable {
         check(surface.isValid) { "Surface $surface isn't valid!" }
         val surfaceToken: SurfaceToken
         var listenersToInvoke: List<SurfaceListener>? = null
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
index 52b9d83..54c54c1 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/Camera2MetadataCache.kt
@@ -24,6 +24,7 @@
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.CameraMetadata
 import androidx.camera.camera2.pipe.CameraPipe
+import androidx.camera.camera2.pipe.config.CameraPipeContext
 import androidx.camera.camera2.pipe.core.Debug
 import androidx.camera.camera2.pipe.core.Log
 import androidx.camera.camera2.pipe.core.Permissions
@@ -44,7 +45,7 @@
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 @Singleton
 internal class Camera2MetadataCache @Inject constructor(
-    private val context: Context,
+    @CameraPipeContext private val cameraPipeContext: Context,
     private val threads: Threads,
     private val permissions: Permissions,
     private val cameraMetadataConfig: CameraPipe.CameraMetadataConfig,
@@ -93,7 +94,7 @@
         return Debug.trace("Camera-${cameraId.value}#readCameraMetadata") {
             try {
                 val cameraManager =
-                    context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
+                    cameraPipeContext.getSystemService(Context.CAMERA_SERVICE) as CameraManager
                 val characteristics =
                     cameraManager.getCameraCharacteristics(cameraId.value)
 
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionState.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionState.kt
index 33149bc..93709b7 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionState.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionState.kt
@@ -32,7 +32,6 @@
 import androidx.camera.camera2.pipe.core.Timestamps.formatMs
 import androidx.camera.camera2.pipe.graph.GraphListener
 import androidx.camera.camera2.pipe.graph.GraphRequestProcessor
-import java.io.Closeable
 import java.util.Collections.synchronizedMap
 import kotlinx.atomicfu.atomic
 import kotlinx.coroutines.CoroutineScope
@@ -57,6 +56,7 @@
  *
  * This class is thread safe.
  */
+@RequiresApi(21)
 internal class CaptureSessionState(
     private val graphListener: GraphListener,
     private val captureSessionFactory: CaptureSessionFactory,
@@ -111,7 +111,7 @@
     private var _surfaceMap: Map<StreamId, Surface>? = null
 
     @GuardedBy("lock")
-    private val _surfaceTokenMap: MutableMap<Surface, Closeable> = mutableMapOf()
+    private val _surfaceTokenMap: MutableMap<Surface, AutoCloseable> = mutableMapOf()
     fun configureSurfaceMap(surfaces: Map<StreamId, Surface>) {
         synchronized(lock) {
             if (state == State.CLOSING || state == State.CLOSED) {
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionWrapper.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionWrapper.kt
index a6ae372..4141f26 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionWrapper.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/compat/CaptureSessionWrapper.kt
@@ -29,7 +29,6 @@
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.UnsafeWrapper
 import androidx.camera.camera2.pipe.core.Log
-import java.io.Closeable
 import kotlin.reflect.KClass
 import kotlinx.atomicfu.atomic
 
@@ -39,7 +38,7 @@
  * This interface has been modified to correct nullness, adjust exceptions, and to return or produce
  * wrapper interfaces instead of the native Camera2 types.
  */
-internal interface CameraCaptureSessionWrapper : UnsafeWrapper, Closeable {
+internal interface CameraCaptureSessionWrapper : UnsafeWrapper, AutoCloseable {
 
     /**
      * @see [CameraCaptureSession.getDevice]
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/Camera2Component.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/Camera2Component.kt
index 48a4ce9..62ec6c7 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/Camera2Component.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/Camera2Component.kt
@@ -51,7 +51,7 @@
 )
 internal abstract class Camera2Module {
     @Binds
-    @CameraPipeCameraBackend
+    @DefaultCameraBackend
     abstract fun bindCameraPipeCameraBackend(camera2Backend: Camera2Backend): CameraBackend
 
     @Binds
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraGraphComponent.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraGraphComponent.kt
index b83853a..dfc8ad4 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraGraphComponent.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraGraphComponent.kt
@@ -18,6 +18,7 @@
 
 package androidx.camera.camera2.pipe.config
 
+import android.content.Context
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.CameraBackend
 import androidx.camera.camera2.pipe.CameraBackends
@@ -48,6 +49,9 @@
 @Qualifier
 internal annotation class ForCameraGraph
 
+@Qualifier
+internal annotation class CameraGraphContext
+
 @CameraGraphScope
 @Subcomponent(
     modules = [
@@ -85,6 +89,10 @@
     @Binds
     abstract fun bindGraphListener(graphProcessor: GraphProcessorImpl): GraphListener
 
+    @Binds
+    @CameraGraphContext
+    abstract fun bindCameraGraphContext(@CameraPipeContext cameraPipeContext: Context): Context
+
     companion object {
         @CameraGraphScope
         @Provides
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraPipeComponent.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraPipeComponent.kt
index d718f2b..d829bef 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraPipeComponent.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/config/CameraPipeComponent.kt
@@ -49,7 +49,11 @@
 import javax.inject.Singleton
 
 @Qualifier
-internal annotation class CameraPipeCameraBackend
+internal annotation class DefaultCameraBackend
+
+/** Qualifier for requesting the CameraPipe scoped Context object */
+@Qualifier
+internal annotation class CameraPipeContext
 
 @Singleton
 @Component(
@@ -91,6 +95,7 @@
 
     companion object {
         @Provides
+        @CameraPipeContext
         fun provideContext(config: CameraPipe.Config): Context = config.appContext
 
         @Provides
@@ -99,25 +104,28 @@
 
         @Reusable
         @Provides
-        fun provideCameraManager(context: Context): CameraManager =
-            context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
+        fun provideCameraManager(@CameraPipeContext cameraPipeContext: Context): CameraManager =
+            cameraPipeContext.getSystemService(Context.CAMERA_SERVICE) as CameraManager
 
         @Reusable
         @Provides
-        fun provideDevicePolicyManagerWrapper(context: Context): DevicePolicyManagerWrapper =
-            AndroidDevicePolicyManagerWrapper(
-                context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
-            )
+        fun provideDevicePolicyManagerWrapper(
+            @CameraPipeContext cameraPipeContext: Context
+        ): DevicePolicyManagerWrapper {
+            val devicePolicyService =
+                cameraPipeContext.getSystemService(Context.DEVICE_POLICY_SERVICE)
+            return AndroidDevicePolicyManagerWrapper(devicePolicyService as DevicePolicyManager)
+        }
 
         @Singleton
         @Provides
         fun provideCameraContext(
-            context: Context,
+            @CameraPipeContext cameraPipeContext: Context,
             threads: Threads,
             cameraBackends: CameraBackends
         ): CameraContext =
             object : CameraContext {
-                override val appContext: Context = context
+                override val appContext: Context = cameraPipeContext
                 override val threads: Threads = threads
                 override val cameraBackends: CameraBackends = cameraBackends
             }
@@ -126,15 +134,15 @@
         @Provides
         fun provideCameraBackends(
             config: CameraPipe.Config,
-            @CameraPipeCameraBackend cameraPipeCameraBackend: Provider<CameraBackend>,
-            appContext: Context,
+            @DefaultCameraBackend defaultCameraBackend: Provider<CameraBackend>,
+            @CameraPipeContext cameraPipeContext: Context,
             threads: Threads,
         ): CameraBackends {
             // This is intentionally lazy. If an internalBackend is defined as part of the
             // CameraPipe configuration, we will never create the default cameraPipeCameraBackend.
             val internalBackend = config.cameraBackendConfig.internalBackend
-                ?: Debug.trace("Initialize cameraPipeCameraBackend") {
-                    cameraPipeCameraBackend.get()
+                ?: Debug.trace("Initialize defaultCameraBackend") {
+                    defaultCameraBackend.get()
                 }
 
             // Make sure that the list of additional backends does not contain the
@@ -151,7 +159,7 @@
                 "Failed to find $defaultBackendId in the list of available CameraPipe backends! " +
                     "Available values are ${allBackends.keys}"
             }
-            return CameraBackendsImpl(defaultBackendId, allBackends, appContext, threads)
+            return CameraBackendsImpl(defaultBackendId, allBackends, cameraPipeContext, threads)
         }
     }
 }
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Permissions.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Permissions.kt
index 81d1205..4d3263d 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Permissions.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/Permissions.kt
@@ -22,6 +22,7 @@
 import android.os.Build
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.compat.Api23Compat
+import androidx.camera.camera2.pipe.config.CameraPipeContext
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -33,7 +34,9 @@
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 @Singleton
-internal class Permissions @Inject constructor(private val context: Context) {
+internal class Permissions @Inject constructor(
+    @CameraPipeContext private val cameraPipeContext: Context
+) {
     @Volatile
     private var _hasCameraPermission = false
     val hasCameraPermission: Boolean
@@ -53,7 +56,7 @@
         // allowing the code to avoid re-querying after checkSelfPermission returns true.
         if (!_hasCameraPermission) {
             Debug.traceStart { "CXCP#checkCameraPermission" }
-            if (Api23Compat.checkSelfPermission(context, Manifest.permission.CAMERA)
+            if (Api23Compat.checkSelfPermission(cameraPipeContext, Manifest.permission.CAMERA)
                 == PERMISSION_GRANTED
             ) {
                 _hasCameraPermission = true
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/TokenLock.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/TokenLock.kt
index 574cc10..2e4d0d9 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/TokenLock.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/core/TokenLock.kt
@@ -22,7 +22,6 @@
 import androidx.annotation.GuardedBy
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.core.TokenLock.Token
-import java.io.Closeable
 import java.util.ArrayDeque
 import kotlin.coroutines.resume
 import kotlin.coroutines.resumeWithException
@@ -47,7 +46,7 @@
  * Access the methods and properties of the [TokenLock] are ThreadSafe, and closing this object
  * multiple times has no effect.
  */
-internal interface TokenLock : AutoCloseable, Closeable {
+internal interface TokenLock : AutoCloseable {
     val capacity: Long
     val available: Long
     val size: Long
@@ -75,7 +74,7 @@
      *
      * Closing this object multiple times has no effect.
      */
-    interface Token : AutoCloseable, Closeable {
+    interface Token : AutoCloseable {
         val value: Long
 
         /**
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/SurfaceGraph.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/SurfaceGraph.kt
index 6f1b8e7..12acb8c 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/SurfaceGraph.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/SurfaceGraph.kt
@@ -25,7 +25,6 @@
 import androidx.camera.camera2.pipe.StreamId
 import androidx.camera.camera2.pipe.config.CameraGraphScope
 import androidx.camera.camera2.pipe.core.Log
-import java.io.Closeable
 import javax.inject.Inject
 
 /**
@@ -47,7 +46,7 @@
     private val surfaceMap: MutableMap<StreamId, Surface> = mutableMapOf()
 
     @GuardedBy("lock")
-    private val surfaceUsageMap: MutableMap<Surface, Closeable> = mutableMapOf()
+    private val surfaceUsageMap: MutableMap<Surface, AutoCloseable> = mutableMapOf()
 
     @GuardedBy("lock")
     private val closed: Boolean = false
@@ -66,7 +65,7 @@
                     "Removed surface for $streamId"
                 }
             }
-            var oldSurfaceToken: Closeable? = null
+            var oldSurfaceToken: AutoCloseable? = null
 
             if (surface == null) {
                 // TODO: Tell the graph processor that it should resubmit the repeating request or
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraBackendsImpl.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraBackendsImpl.kt
index e7ed8fc..9d2d818 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraBackendsImpl.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/internal/CameraBackendsImpl.kt
@@ -23,6 +23,7 @@
 import androidx.camera.camera2.pipe.CameraBackendId
 import androidx.camera.camera2.pipe.CameraBackends
 import androidx.camera.camera2.pipe.CameraContext
+import androidx.camera.camera2.pipe.config.CameraPipeContext
 import androidx.camera.camera2.pipe.core.Threads
 
 /**
@@ -31,7 +32,7 @@
 internal class CameraBackendsImpl(
     private val defaultBackendId: CameraBackendId,
     private val cameraBackends: Map<CameraBackendId, CameraBackendFactory>,
-    private val appContext: Context,
+    @CameraPipeContext private val cameraPipeContext: Context,
     private val threads: Threads
 ) : CameraBackends {
     private val lock = Any()
@@ -55,7 +56,7 @@
             if (existing != null) return existing
 
             val backend = cameraBackends[backendId]?.create(
-                CameraBackendContext(appContext, threads, this)
+                CameraBackendContext(cameraPipeContext, threads, this)
             )
             if (backend != null) {
                 check(backendId == backend.id) {
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphTestContext.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphTestContext.kt
index 5077416..14239c7 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphTestContext.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphTestContext.kt
@@ -22,9 +22,8 @@
 import androidx.camera.camera2.pipe.StreamId
 import androidx.camera.camera2.pipe.testing.FakeCaptureSequenceProcessor
 import androidx.camera.camera2.pipe.testing.FakeGraphProcessor
-import java.io.Closeable
 
-internal class GraphTestContext : Closeable {
+internal class GraphTestContext : AutoCloseable {
     val streamId = StreamId(0)
     val surfaceMap = mapOf(streamId to Surface(SurfaceTexture(1)))
     val captureSequenceProcessor = FakeCaptureSequenceProcessor()
diff --git a/camera/camera-extensions-stub/camera-extensions-stub.jar b/camera/camera-extensions-stub/camera-extensions-stub.jar
deleted file mode 100644
index fe03193..0000000
--- a/camera/camera-extensions-stub/camera-extensions-stub.jar
+++ /dev/null
Binary files differ
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImpl.java
index 68de01b..61b01ef 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/Camera2OutputConfigImpl.java
@@ -23,6 +23,8 @@
 /**
  * A config representing a {@link android.hardware.camera2.params.OutputConfiguration} where
  * Surface will be created by the information in this config.
+ *
+ * @since 1.2
  */
 @SuppressLint("UnknownNullness")
 public interface Camera2OutputConfigImpl {
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImpl.java
index d121717..b470063 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/Camera2SessionConfigImpl.java
@@ -24,6 +24,8 @@
 
 /**
  * A config representing a {@link android.hardware.camera2.params.SessionConfiguration}
+ *
+ * @since 1.2
  */
 @SuppressLint("UnknownNullness")
 public interface Camera2SessionConfigImpl {
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/ImageProcessorImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/ImageProcessorImpl.java
index ce17c4f..6209e0c 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/ImageProcessorImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/ImageProcessorImpl.java
@@ -22,6 +22,8 @@
  * A interface to receive and process the upcoming next available Image.
  *
  * <p>Implemented by OEM.
+ *
+ * @since 1.2
  */
 @SuppressLint("UnknownNullness")
 public interface ImageProcessorImpl {
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/ImageReaderOutputConfigImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/ImageReaderOutputConfigImpl.java
index ca4dcaf..a58f8c4 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/ImageReaderOutputConfigImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/ImageReaderOutputConfigImpl.java
@@ -21,6 +21,8 @@
 
 /**
  * Surface will be created by constructing a ImageReader.
+ *
+ * @since 1.2
  */
 @SuppressLint("UnknownNullness")
 public interface ImageReaderOutputConfigImpl extends Camera2OutputConfigImpl {
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/ImageReferenceImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/ImageReferenceImpl.java
index 95f2c3b..aafba7d 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/ImageReferenceImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/ImageReferenceImpl.java
@@ -25,6 +25,8 @@
  * reaches 0.
  *
  * <p>Implemented by Camera2/CameraX.
+ *
+ * @since 1.2
  */
 @SuppressLint("UnknownNullness")
 public interface ImageReferenceImpl {
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/MultiResolutionImageReaderOutputConfigImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/MultiResolutionImageReaderOutputConfigImpl.java
index c3ad61b..ccc229d 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/MultiResolutionImageReaderOutputConfigImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/MultiResolutionImageReaderOutputConfigImpl.java
@@ -18,6 +18,8 @@
 
 /**
  * Surface will be created by constructing a MultiResolutionImageReader.
+ *
+ * @since 1.2
  */
 public interface MultiResolutionImageReaderOutputConfigImpl extends Camera2OutputConfigImpl {
     /**
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/OutputSurfaceImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/OutputSurfaceImpl.java
index f692029..fe93f85 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/OutputSurfaceImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/OutputSurfaceImpl.java
@@ -22,6 +22,8 @@
 
 /**
  * For specifying output surface of the extension.
+ *
+ * @since 1.2
  */
 @SuppressLint("UnknownNullness")
 public interface OutputSurfaceImpl {
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/RequestProcessorImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/RequestProcessorImpl.java
index 5185333..003f105 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/RequestProcessorImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/RequestProcessorImpl.java
@@ -27,6 +27,8 @@
 
 /**
  * An Interface to execute Camera2 capture requests.
+ *
+ * @since 1.2
  */
 @SuppressLint("UnknownNullness")
 public interface RequestProcessorImpl {
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java
index fabfc2b..b87f4bd 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/SessionProcessorImpl.java
@@ -56,6 +56,8 @@
  *
  * (6) {@link #deInitSession}: called when CameraCaptureSession is closed.
  * </pre>
+ *
+ * @since 1.2
  */
 @SuppressLint("UnknownNullness")
 public interface SessionProcessorImpl {
@@ -274,6 +276,8 @@
          *                             as part of this callback. Both Camera2 and CameraX guarantee
          *                             that those two settings and results are always supported and
          *                             applied by the corresponding framework.
+         *
+         * @since 1.3
          */
         void onCaptureCompleted(long timestamp, int captureSequenceId,
                 Map<CaptureResult.Key, Object> result);
diff --git a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/SurfaceOutputConfigImpl.java b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/SurfaceOutputConfigImpl.java
index 7b8d83c..1a23ce0 100644
--- a/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/SurfaceOutputConfigImpl.java
+++ b/camera/camera-extensions-stub/src/main/java/androidx/camera/extensions/impl/advanced/SurfaceOutputConfigImpl.java
@@ -21,6 +21,8 @@
 
 /**
  * Use Surface directly to create the OutputConfiguration.
+ *
+ * @since 1.2
  */
 @SuppressLint("UnknownNullness")
 public interface SurfaceOutputConfigImpl extends Camera2OutputConfigImpl {
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/StillCaptureProcessorTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/StillCaptureProcessorTest.kt
index 401ad8d..6435358 100644
--- a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/StillCaptureProcessorTest.kt
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/sessionprocessor/StillCaptureProcessorTest.kt
@@ -43,6 +43,7 @@
 import androidx.camera.testing.CameraUtil
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
 import androidx.testutils.assertThrows
@@ -339,6 +340,7 @@
         }
     }
 
+    @FlakyTest(bugId = 265008341)
     @Test
     fun canCloseBeforeJpegConversion(): Unit = runBlocking {
         withTimeout(3000) {
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/DeviceCompatibilityTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/DeviceCompatibilityTest.kt
index ac508e5..7892f10 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/DeviceCompatibilityTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/DeviceCompatibilityTest.kt
@@ -70,6 +70,11 @@
         active = implName == CameraPipeConfig::class.simpleName,
     )
 
+    @get:Rule
+    val cameraRule = CameraUtil.grantCameraPermissionAndPreTest(
+        CameraUtil.PreTestCameraIdList(cameraConfig)
+    )
+
     @Before
     fun setup() {
         CameraXUtil.initialize(context, cameraConfig).get()
@@ -124,6 +129,9 @@
     }
 
     private fun getSupportedProfiles(cameraSelector: CameraSelector): List<CamcorderProfileProxy> {
+        if (!CameraUtil.hasCameraWithLensFacing(cameraSelector.lensFacing!!)) {
+            return emptyList()
+        }
         val cameraInfo = CameraUtil.createCameraUseCaseAdapter(context, cameraSelector).cameraInfo
         val videoCapabilities = VideoCapabilities.from(cameraInfo)
         return videoCapabilities.supportedQualities
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
index 380f24b..29c53eb 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
@@ -20,6 +20,7 @@
 import android.Manifest
 import android.annotation.SuppressLint
 import android.app.AppOpsManager
+import android.app.AppOpsManager.OnOpNotedCallback
 import android.app.AsyncNotedAppOp
 import android.app.SyncNotedAppOp
 import android.content.ContentResolver
@@ -41,8 +42,9 @@
 import androidx.camera.core.Preview
 import androidx.camera.core.SurfaceRequest
 import androidx.camera.core.impl.ImageFormatConstants
-import androidx.camera.core.impl.Observable
-import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.core.impl.Observable.Observer
+import androidx.camera.core.impl.utils.executor.CameraXExecutors.directExecutor
+import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 import androidx.camera.core.internal.CameraUseCaseAdapter
 import androidx.camera.testing.AudioUtil
 import androidx.camera.testing.CameraPipeConfigTestRule
@@ -55,18 +57,27 @@
 import androidx.camera.testing.mocks.MockConsumer
 import androidx.camera.testing.mocks.helpers.CallTimes
 import androidx.camera.testing.mocks.helpers.CallTimesAtLeast
+import androidx.camera.video.VideoOutput.SourceState.ACTIVE_NON_STREAMING
+import androidx.camera.video.VideoOutput.SourceState.ACTIVE_STREAMING
+import androidx.camera.video.VideoOutput.SourceState.INACTIVE
+import androidx.camera.video.VideoRecordEvent.Finalize
 import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_DURATION_LIMIT_REACHED
 import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_FILE_SIZE_LIMIT_REACHED
 import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_INVALID_OUTPUT_OPTIONS
+import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_NONE
 import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_NO_VALID_DATA
 import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_RECORDER_ERROR
 import androidx.camera.video.VideoRecordEvent.Finalize.ERROR_SOURCE_INACTIVE
+import androidx.camera.video.VideoRecordEvent.Pause
+import androidx.camera.video.VideoRecordEvent.Resume
+import androidx.camera.video.VideoRecordEvent.Start
+import androidx.camera.video.VideoRecordEvent.Status
 import androidx.camera.video.internal.compat.quirk.DeactivateEncoderSurfaceBeforeStopEncoderQuirk
 import androidx.camera.video.internal.compat.quirk.DeviceQuirks
 import androidx.camera.video.internal.compat.quirk.ExtraSupportedResolutionQuirk
 import androidx.camera.video.internal.compat.quirk.MediaStoreVideoCannotWrite
+import androidx.camera.video.internal.encoder.EncoderFactory
 import androidx.camera.video.internal.encoder.InvalidConfigException
-import androidx.core.util.Consumer
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
@@ -77,9 +88,7 @@
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import java.io.File
-import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executor
-import java.util.concurrent.Semaphore
 import java.util.concurrent.TimeUnit
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CompletableDeferred
@@ -104,10 +113,14 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.timeout
 
+private const val DEFAULT_STATUS_COUNT = 5
 private const val GENERAL_TIMEOUT = 5000L
 private const val STATUS_TIMEOUT = 15000L
 private const val TEST_ATTRIBUTION_TAG = "testAttribution"
-private const val BITRATE_AUTO = 0
+// For the file size is small, the final file length possibly exceeds the file size limit
+// after adding the file header. We still add the buffer for the tolerance of comparing the
+// file length and file size limit.
+private const val FILE_SIZE_LIMIT_BUFFER = 50 * 1024 // 50k threshold buffer
 
 @LargeTest
 @RunWith(Parameterized::class)
@@ -156,12 +169,11 @@
     private val instrumentation = InstrumentationRegistry.getInstrumentation()
     private val context: Context = ApplicationProvider.getApplicationContext()
     private val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
+    private val recordingsToStop = mutableListOf<RecordingProcess>()
 
     private lateinit var cameraUseCaseAdapter: CameraUseCaseAdapter
-    private lateinit var recorder: Recorder
     private lateinit var preview: Preview
     private lateinit var surfaceTexturePreview: Preview
-    private lateinit var mockVideoRecordEventConsumer: MockConsumer<VideoRecordEvent>
 
     @Before
     fun setUp() {
@@ -249,19 +261,18 @@
             surfaceTexturePreview,
             preview
         )
-
-        mockVideoRecordEventConsumer = MockConsumer<VideoRecordEvent>()
     }
 
     @After
     fun tearDown() {
+        for (recording in recordingsToStop) {
+            recording.stop()
+        }
+
         if (this::cameraUseCaseAdapter.isInitialized) {
             instrumentation.runOnMainSync {
                 cameraUseCaseAdapter.removeUseCases(cameraUseCaseAdapter.useCases)
             }
-            if (this::recorder.isInitialized) {
-                recorder.onSourceStateChanged(VideoOutput.SourceState.INACTIVE)
-            }
         }
 
         CameraXUtil.shutdown().get(10, TimeUnit.SECONDS)
@@ -269,316 +280,207 @@
 
     @Test
     fun canRecordToFile() {
-        testRecorderIsConfiguredBasedOnTargetVideoEncodingBitrate(BITRATE_AUTO, enableAudio = true)
+        // Arrange.
+        val outputOptions = createFileOutputOptions()
+        val recording = createRecordingProcess(outputOptions = outputOptions)
+
+        // Act.
+        recording.startAndVerify()
+        recording.stopAndVerify { finalize ->
+            // Assert.
+            val uri = finalize.outputResults.outputUri
+            assertThat(uri).isEqualTo(Uri.fromFile(outputOptions.file))
+            checkFileHasAudioAndVideo(uri)
+        }
     }
 
     @Test
     fun recordingWithSetTargetVideoEncodingBitRate() {
         testRecorderIsConfiguredBasedOnTargetVideoEncodingBitrate(6_000_000)
-        verifyConfiguredVideoBitrate()
     }
 
     @Test
     fun recordingWithSetTargetVideoEncodingBitRateOutOfRange() {
         testRecorderIsConfiguredBasedOnTargetVideoEncodingBitrate(1000_000_000)
-        verifyConfiguredVideoBitrate()
     }
 
     @Test
     fun recordingWithNegativeBitRate() {
-        initializeRecorder()
         assertThrows(IllegalArgumentException::class.java) {
-            Recorder.Builder().setTargetVideoEncodingBitRate(-5).build()
+            createRecorder(targetBitrate = -5)
         }
     }
 
     @Test
     fun canRecordToMediaStore() {
-        initializeRecorder()
         assumeTrue(
             "Ignore the test since the MediaStore.Video has compatibility issues.",
             DeviceQuirks.get(MediaStoreVideoCannotWrite::class.java) == null
         )
-        invokeSurfaceRequest()
-        val statusSemaphore = Semaphore(0)
-        val finalizeSemaphore = Semaphore(0)
-        val context: Context = ApplicationProvider.getApplicationContext()
+
+        // Arrange.
         val contentResolver: ContentResolver = context.contentResolver
         val contentValues = ContentValues().apply {
             put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
         }
-
         val outputOptions = MediaStoreOutputOptions.Builder(
             contentResolver,
             MediaStore.Video.Media.EXTERNAL_CONTENT_URI
         ).setContentValues(contentValues).build()
+        val recording = createRecordingProcess(outputOptions = outputOptions)
 
-        var uri: Uri = Uri.EMPTY
-        val recording = recorder.prepareRecording(context, outputOptions)
-            .withAudioEnabled()
-            .start(CameraXExecutors.directExecutor()) {
-                if (it is VideoRecordEvent.Status) {
-                    statusSemaphore.release()
-                }
-                if (it is VideoRecordEvent.Finalize) {
-                    uri = it.outputResults.outputUri
-                    finalizeSemaphore.release()
-                }
-            }
+        // Act.
+        recording.startAndVerify()
+        recording.stopAndVerify { finalize ->
+            // Assert.
+            val uri = finalize.outputResults.outputUri
+            checkFileHasAudioAndVideo(uri)
 
-        assertThat(statusSemaphore.tryAcquire(5, 15000L, TimeUnit.MILLISECONDS)).isTrue()
-
-        recording.stopSafely()
-
-        // Wait for the recording to complete.
-        assertThat(finalizeSemaphore.tryAcquire(GENERAL_TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
-
-        assertThat(uri).isNotEqualTo(Uri.EMPTY)
-
-        checkFileHasAudioAndVideo(uri)
-
-        contentResolver.delete(uri, null, null)
+            // Clean-up.
+            contentResolver.delete(uri, null, null)
+        }
     }
 
     @Test
     @SdkSuppress(minSdkVersion = 26)
     fun canRecordToFileDescriptor() {
-        initializeRecorder()
-        invokeSurfaceRequest()
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val pfd = ParcelFileDescriptor.open(
-            file,
-            ParcelFileDescriptor.MODE_READ_WRITE
-        )
-        val recording = recorder
-            .prepareRecording(context, FileDescriptorOutputOptions.Builder(pfd).build())
-            .withAudioEnabled()
-            .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer)
+        // Arrange.
+        val file = createTempFile()
+        val pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE)
+        val outputOptions = FileDescriptorOutputOptions.Builder(pfd).build()
+        val recording = createRecordingProcess(outputOptions = outputOptions)
 
+        // Act.
+        recording.startAndVerify()
         // ParcelFileDescriptor should be safe to close after PendingRecording#start.
         pfd.close()
+        recording.stopAndVerify()
 
-        mockVideoRecordEventConsumer.verifyRecordingStartSuccessfully()
-
-        recording.stopSafely()
-
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent.Finalize::class.java,
-            true,
-            GENERAL_TIMEOUT
-        )
-
+        // Assert.
         checkFileHasAudioAndVideo(Uri.fromFile(file))
-
-        file.delete()
     }
 
     @Test
     @SdkSuppress(minSdkVersion = 26)
     fun recordToFileDescriptor_withClosedFileDescriptor_receiveError() {
-        initializeRecorder()
-        invokeSurfaceRequest()
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
+        // Arrange.
+        val file = createTempFile()
         val pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE)
-
         pfd.close()
+        val outputOptions = FileDescriptorOutputOptions.Builder(pfd).build()
+        val recording = createRecordingProcess(outputOptions = outputOptions)
 
-        recorder.prepareRecording(context, FileDescriptorOutputOptions.Builder(pfd).build())
-            .withAudioEnabled()
-            .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer)
-
-        // Check the output Uri from the finalize event match the Uri from the given file.
-        val captor = ArgumentCaptorCameraX<VideoRecordEvent> { argument ->
-            VideoRecordEvent::class.java.isInstance(
-                argument
-            )
+        // Act.
+        recording.start()
+        recording.stopAndVerify { finalize ->
+            // Assert.
+            assertThat(finalize.error).isEqualTo(ERROR_INVALID_OUTPUT_OPTIONS)
         }
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent::class.java,
-            false,
-            CallTimesAtLeast(1),
-            captor
-        )
-
-        val finalize = captor.value as VideoRecordEvent.Finalize
-        assertThat(finalize.error).isEqualTo(ERROR_INVALID_OUTPUT_OPTIONS)
-
-        file.delete()
     }
 
     @Test
     @SdkSuppress(minSdkVersion = 21, maxSdkVersion = 25)
     @SuppressLint("NewApi") // Intentionally testing behavior of calling from invalid API level
     fun prepareRecordingWithFileDescriptor_throwsExceptionBeforeApi26() {
-        initializeRecorder()
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
+        // Arrange.
+        val recorder = createRecorder()
+        val file = createTempFile()
         ParcelFileDescriptor.open(
             file,
             ParcelFileDescriptor.MODE_READ_WRITE
         ).use { pfd ->
+            // Assert.
             assertThrows(UnsupportedOperationException::class.java) {
+                // Act.
                 recorder.prepareRecording(context, FileDescriptorOutputOptions.Builder(pfd).build())
             }
         }
-
-        file.delete()
     }
 
     @Test
     fun canPauseResume() {
-        initializeRecorder()
-        invokeSurfaceRequest()
+        // Arrange.
+        val recording = createRecordingProcess()
 
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-
-        recorder.prepareRecording(context, FileOutputOptions.Builder(file).build())
-            .withAudioEnabled()
-            .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer).apply {
-                pause()
-
-                mockVideoRecordEventConsumer.verifyAcceptCall(
-                    VideoRecordEvent.Pause::class.java,
-                    true, GENERAL_TIMEOUT
-                )
-
-                resume()
-
-                mockVideoRecordEventConsumer.verifyAcceptCall(
-                    VideoRecordEvent.Resume::class.java,
-                    true, GENERAL_TIMEOUT
-                )
-                // Check there are data being encoded after resuming.
-                mockVideoRecordEventConsumer.verifyAcceptCall(
-                    VideoRecordEvent.Status::class.java,
-                    true, STATUS_TIMEOUT, CallTimesAtLeast(5)
-                )
-
-                stopSafely()
-            }
-
-        // Wait for the recording to be finalized.
-        mockVideoRecordEventConsumer.verifyAcceptCall(VideoRecordEvent.Finalize::class.java,
-            true, GENERAL_TIMEOUT)
-
-        checkFileHasAudioAndVideo(Uri.fromFile(file))
-
-        file.delete()
+        // Act.
+        recording.startAndVerify()
+        recording.pauseAndVerify()
+        recording.resumeAndVerify()
+        recording.stopAndVerify { finalize ->
+            // Assert.
+            assertThat(finalize.error).isEqualTo(ERROR_NONE)
+        }
     }
 
     @Test
     fun canStartRecordingPaused_whenRecorderInitializing() {
-        initializeRecorder()
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
+        // Arrange.
+        val recorder = createRecorder(sendSurfaceRequest = false)
+        val recording = createRecordingProcess(recorder = recorder)
 
-        recorder.prepareRecording(context, FileOutputOptions.Builder(file).build())
-            .withAudioEnabled()
-            .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer).apply {
-                pause()
+        // Act.
+        recording.start()
+        recording.pause()
+        // Only invoke surface request after pause() has been called
+        recorder.sendSurfaceRequest()
 
-                // Only invoke surface request after pause() has been called
-                invokeSurfaceRequest()
-
-                mockVideoRecordEventConsumer.verifyAcceptCall(
-                    VideoRecordEvent.Start::class.java,
-                    true, GENERAL_TIMEOUT
-                )
-                mockVideoRecordEventConsumer.verifyAcceptCall(
-                    VideoRecordEvent.Pause::class.java,
-                    true, GENERAL_TIMEOUT
-                )
-
-                stopSafely()
-            }
-
-        file.delete()
+        // Assert.
+        recording.verifyStart()
+        recording.verifyPause()
     }
 
     @Test
     fun canReceiveRecordingStats() {
-        initializeRecorder()
-        invokeSurfaceRequest()
+        // Arrange.
+        val recording = createRecordingProcess()
 
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
+        // Act.
+        recording.startAndVerify()
+        recording.pauseAndVerify()
+        recording.resumeAndVerify()
+        recording.stopAndVerify()
 
-        // Start
-        recorder.prepareRecording(context, FileOutputOptions.Builder(file).build())
-            .withAudioEnabled()
-            .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer).apply {
-                mockVideoRecordEventConsumer.verifyRecordingStartSuccessfully()
-
-                pause()
-
-                mockVideoRecordEventConsumer.verifyAcceptCall(
-                    VideoRecordEvent.Pause::class.java,
-                    true, GENERAL_TIMEOUT
-                )
-
-                resume()
-
-                mockVideoRecordEventConsumer.verifyAcceptCall(
-                    VideoRecordEvent.Resume::class.java,
-                    true, GENERAL_TIMEOUT
-                )
-                mockVideoRecordEventConsumer.verifyAcceptCall(
-                    VideoRecordEvent.Status::class.java,
-                    true, STATUS_TIMEOUT, CallTimesAtLeast(5)
-                )
-
-                stopSafely()
-
-                mockVideoRecordEventConsumer.verifyAcceptCall(
-                    VideoRecordEvent.Finalize::class.java,
-                    true, GENERAL_TIMEOUT
-                )
-            }
-
-        val captor = ArgumentCaptorCameraX<VideoRecordEvent> { argument ->
-            VideoRecordEvent::class.java.isInstance(
-                argument
-            )
-        }
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent::class.java,
-            false,
-            CallTimesAtLeast(1),
-            captor
+        // Assert.
+        val events = recording.getAllEvents()
+        assertThat(events.size).isAtLeast(
+            1 /* Start */ +
+                5 /* Status */ +
+                1 /* Pause */ +
+                1 /* Resume */ +
+                5 /* Status */ +
+                1 /* Stop */
         )
 
-        captor.allValues.run {
-            assertThat(size).isAtLeast(
-                (
-                    1 /* Start */ +
-                        5 /* Status */ +
-                        1 /* Pause */ +
-                        1 /* Resume */ +
-                        5 /* Status */ +
-                        1 /* Stop */
-                    )
-            )
-
-            // Ensure duration and bytes are increasing
-            take(size - 1).mapIndexed { index, _ ->
-                Pair(get(index).recordingStats, get(index + 1).recordingStats)
-            }.forEach { (former: RecordingStats, latter: RecordingStats) ->
-                assertThat(former.numBytesRecorded).isAtMost(latter.numBytesRecorded)
-                assertThat(former.recordedDurationNanos).isAtMost((latter.recordedDurationNanos))
-            }
-
-            // Ensure they are not all zero by checking last stats
-            last().recordingStats.also {
-                assertThat(it.numBytesRecorded).isGreaterThan(0L)
-                assertThat(it.recordedDurationNanos).isGreaterThan(0L)
-            }
+        // Assert: Ensure duration and bytes are increasing.
+        List(events.size - 1) { index ->
+            Pair(events[index].recordingStats, events[index + 1].recordingStats)
+        }.forEach { (former: RecordingStats, latter: RecordingStats) ->
+            assertThat(former.numBytesRecorded).isAtMost(latter.numBytesRecorded)
+            assertThat(former.recordedDurationNanos).isAtMost((latter.recordedDurationNanos))
         }
 
-        file.delete()
+        // Assert: Ensure they are not all zero by checking the last stats.
+        events.last().recordingStats.also {
+            assertThat(it.numBytesRecorded).isGreaterThan(0L)
+            assertThat(it.recordedDurationNanos).isGreaterThan(0L)
+        }
     }
 
     @Test
     fun setFileSizeLimit() {
-        initializeRecorder()
+        // Arrange.
         val fileSizeLimit = 500L * 1024L // 500 KB
-        runFileSizeLimitTest(fileSizeLimit)
+        val outputOptions = createFileOutputOptions(fileSizeLimit = fileSizeLimit)
+        val recording = createRecordingProcess(outputOptions = outputOptions)
+
+        // Act.
+        recording.startAndVerify()
+        recording.verifyFinalize(timeoutMs = 60_000L) { finalize ->
+            // Assert.
+            assertThat(finalize.error).isEqualTo(ERROR_FILE_SIZE_LIMIT_REACHED)
+            assertThat(outputOptions.file.length())
+                .isLessThan(fileSizeLimit + FILE_SIZE_LIMIT_BUFFER)
+        }
     }
 
     // Sets the file size limit to 1 byte, which will be lower than the initial data sent from
@@ -586,800 +488,429 @@
     // written to it.
     @Test
     fun setFileSizeLimitLowerThanInitialDataSize() {
-        initializeRecorder()
+        // Arrange.
         val fileSizeLimit = 1L // 1 byte
-        runFileSizeLimitTest(fileSizeLimit)
+        val outputOptions = createFileOutputOptions(fileSizeLimit = fileSizeLimit)
+        val recording = createRecordingProcess(outputOptions = outputOptions)
+
+        // Act.
+        recording.start()
+        recording.verifyFinalize { finalize ->
+            // Assert.
+            assertThat(finalize.error).isEqualTo(ERROR_FILE_SIZE_LIMIT_REACHED)
+        }
     }
 
     @Test
     fun setLocation() {
-        initializeRecorder()
         runLocationTest(createLocation(25.033267462243586, 121.56454121737946))
     }
 
     @Test
     fun setNegativeLocation() {
-        initializeRecorder()
         runLocationTest(createLocation(-27.14394722411734, -109.33053675296067))
     }
 
     @Test
     fun stop_withErrorWhenDurationLimitReached() {
-        initializeRecorder()
-        val videoRecordEventListener = MockConsumer<VideoRecordEvent>()
-        invokeSurfaceRequest()
+        // Arrange.
         val durationLimitMs = 3000L
-        val durationTolerance = 50L
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.Builder(file)
-            .setDurationLimitMillis(durationLimitMs)
-            .build()
+        val durationToleranceMs = 50L
+        val outputOptions = createFileOutputOptions(durationLimitMillis = durationLimitMs)
+        val recording = createRecordingProcess(outputOptions = outputOptions)
 
-        val recording = recorder
-            .prepareRecording(context, outputOptions)
-            .withAudioEnabled()
-            .start(CameraXExecutors.directExecutor(), videoRecordEventListener)
+        // Act.
+        recording.start()
 
-        // The recording should be finalized after the specified duration limit plus some time
-        // for processing it.
-        videoRecordEventListener.verifyAcceptCall(
-            VideoRecordEvent.Finalize::class.java,
-            false,
-            durationLimitMs + 2000L
-        )
-
-        val captor = ArgumentCaptorCameraX<VideoRecordEvent> {
-                argument -> VideoRecordEvent::class.java.isInstance(argument)
+        // Assert.
+        recording.verifyFinalize(timeoutMs = durationLimitMs + 2000L) { finalize ->
+            // Assert.
+            assertThat(finalize.error).isEqualTo(ERROR_DURATION_LIMIT_REACHED)
+            assertThat(finalize.recordingStats.recordedDurationNanos)
+                .isAtMost(TimeUnit.MILLISECONDS.toNanos(durationLimitMs + durationToleranceMs))
+            checkDurationAtMost(
+                Uri.fromFile(outputOptions.file),
+                durationLimitMs + durationToleranceMs
+            )
         }
-        videoRecordEventListener.verifyAcceptCall(VideoRecordEvent::class.java,
-            false, CallTimesAtLeast(1), captor)
-
-        val finalize = captor.value as VideoRecordEvent.Finalize
-        assertThat(finalize.error).isEqualTo(ERROR_DURATION_LIMIT_REACHED)
-        assertThat(finalize.recordingStats.recordedDurationNanos)
-            .isAtMost(TimeUnit.MILLISECONDS.toNanos(durationLimitMs + durationTolerance))
-        checkDurationAtMost(Uri.fromFile(file), durationLimitMs)
-
-        recording.stopSafely()
-        file.delete()
     }
 
     @Test
     fun checkStreamState() {
-        initializeRecorder()
-        invokeSurfaceRequest()
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-
+        // Arrange.
+        val recorder = createRecorder()
         @Suppress("UNCHECKED_CAST")
-        val streamInfoObserver =
-            mock(Observable.Observer::class.java) as Observable.Observer<StreamInfo>
+        val streamInfoObserver = mock(Observer::class.java) as Observer<StreamInfo>
         val inOrder = inOrder(streamInfoObserver)
-        recorder.streamInfo.addObserver(CameraXExecutors.directExecutor(), streamInfoObserver)
+        recorder.streamInfo.addObserver(directExecutor(), streamInfoObserver)
 
-        // Recorder should start in INACTIVE stream state before any recordings
-        inOrder.verify(streamInfoObserver, timeout(5000L)).onNewData(
-            argThat {
-                it!!.streamState == StreamInfo.StreamState.INACTIVE
-            }
-        )
-
-        // Start
-        val recording =
-            recorder.prepareRecording(context, FileOutputOptions.Builder(file).build())
-                .withAudioEnabled()
-                .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer)
-        // Starting recording should move Recorder to ACTIVE stream state
-        inOrder.verify(streamInfoObserver, timeout(5000L)).onNewData(
-            argThat {
-                it!!.streamState == StreamInfo.StreamState.ACTIVE
-            }
-        )
-
-        recording.stopSafely()
-
-        // Stopping recording should eventually move to INACTIVE stream state
+        // Assert: Recorder should start in INACTIVE stream state before any recordings
         inOrder.verify(streamInfoObserver, timeout(GENERAL_TIMEOUT)).onNewData(
             argThat {
                 it!!.streamState == StreamInfo.StreamState.INACTIVE
             }
         )
+        val recording = createRecordingProcess(recorder = recorder)
 
-        file.delete()
+        // Act.
+        recording.start()
+
+        // Assert: Starting recording should move Recorder to ACTIVE stream state
+        inOrder.verify(streamInfoObserver, timeout(5000L)).onNewData(
+            argThat { it!!.streamState == StreamInfo.StreamState.ACTIVE }
+        )
+
+        // Act.
+        recording.stop()
+
+        // Assert: Stopping recording should eventually move to INACTIVE stream state
+        inOrder.verify(streamInfoObserver, timeout(GENERAL_TIMEOUT)).onNewData(
+            argThat {
+                it!!.streamState == StreamInfo.StreamState.INACTIVE
+            }
+        )
     }
 
     @Test
     fun start_throwsExceptionWhenActive() {
-        initializeRecorder()
-        invokeSurfaceRequest()
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.Builder(file).build()
+        // Arrange.
+        val recorder = createRecorder()
+        val recording = createRecordingProcess(recorder = recorder)
 
-        val recording = recorder.prepareRecording(context, outputOptions).start(
-            CameraXExecutors.directExecutor()
-        ) {}
+        // Act: 1st start.
+        recording.start()
 
-        val pendingRecording = recorder.prepareRecording(context, outputOptions)
+        // Assert.
         assertThrows(java.lang.IllegalStateException::class.java) {
-            pendingRecording.start(CameraXExecutors.directExecutor()) {}
+            // Act: 2nd start.
+            val recording2 = createRecordingProcess(recorder = recorder)
+            recording2.start()
         }
-
-        recording.close()
-        file.delete()
     }
 
     @Test
     fun start_whenSourceActiveNonStreaming() {
-        initializeRecorder()
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
+        // Arrange.
+        val recorder = createRecorder(initSourceState = ACTIVE_NON_STREAMING)
+        val recording = createRecordingProcess(recorder = recorder)
 
-        recorder.onSourceStateChanged(VideoOutput.SourceState.ACTIVE_NON_STREAMING)
-
-        val recording =
-            recorder.prepareRecording(context, FileOutputOptions.Builder(file).build())
-                .withAudioEnabled()
-                .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer)
-
-        invokeSurfaceRequest()
-
-        mockVideoRecordEventConsumer.verifyRecordingStartSuccessfully()
-
-        recording.stopSafely()
-        // Wait for the recording to be finalized.
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent.Finalize::class.java,
-            true, GENERAL_TIMEOUT
-        )
-
-        file.delete()
+        // Act.
+        recording.start()
+        recorder.onSourceStateChanged(ACTIVE_STREAMING)
+        recording.verifyStart()
+        recording.verifyStatus()
+        recording.stopAndVerify { finalize ->
+            // Assert.
+            assertThat(finalize.error).isEqualTo(ERROR_NONE)
+        }
     }
 
     @Test
     fun start_finalizeImmediatelyWhenSourceInactive() {
-        initializeRecorder()
-        invokeSurfaceRequest()
-        val videoCaptureMonitor = VideoCaptureMonitor()
-        recorder.startVideoRecording(temporaryFolder.newFile(), videoCaptureMonitor).use {
-            // Ensure the Recorder is initialized before start test.
-            videoCaptureMonitor.waitForVideoCaptureStatus()
+        // Arrange.
+        val recorder = createRecorder(initSourceState = INACTIVE)
+        val recording = createRecordingProcess(recorder = recorder)
+
+        // Act.
+        recording.start()
+
+        // Assert.
+        recording.verifyFinalize { finalize ->
+            // Assert.
+            assertThat(finalize.error).isEqualTo(ERROR_SOURCE_INACTIVE)
         }
-
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-
-        recorder.onSourceStateChanged(VideoOutput.SourceState.INACTIVE)
-
-        val recording =
-            recorder.prepareRecording(context, FileOutputOptions.Builder(file).build())
-                .withAudioEnabled()
-                .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer)
-
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent.Finalize::class.java,
-            false, GENERAL_TIMEOUT
-        )
-        mockVideoRecordEventConsumer.verifyNoMoreAcceptCalls(false)
-
-        val captor = ArgumentCaptorCameraX<VideoRecordEvent> { argument ->
-            VideoRecordEvent::class.java.isInstance(
-                argument
-            )
-        }
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent::class.java,
-            false,
-            CallTimesAtLeast(1),
-            captor
-        )
-
-        val finalize = captor.value as VideoRecordEvent.Finalize
-        assertThat(finalize.error).isEqualTo(ERROR_SOURCE_INACTIVE)
-
-        recording.stopSafely()
-
-        file.delete()
     }
 
     @Test
     fun pause_whenSourceActiveNonStreaming() {
-        initializeRecorder()
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-
-        recorder.onSourceStateChanged(VideoOutput.SourceState.ACTIVE_NON_STREAMING)
-
-        recorder.prepareRecording(context, FileOutputOptions.Builder(file).build())
-            .withAudioEnabled()
-            .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer).apply {
-                pause()
-
-                invokeSurfaceRequest()
-
-                mockVideoRecordEventConsumer.verifyAcceptCall(
-                    VideoRecordEvent.Start::class.java,
-                    true, GENERAL_TIMEOUT
-                )
-                mockVideoRecordEventConsumer.verifyAcceptCall(
-                    VideoRecordEvent.Pause::class.java,
-                    true, GENERAL_TIMEOUT
-                )
-
-                stopSafely()
-            }
-
-        // Wait for the recording to be finalized.
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent.Finalize::class.java,
-            true, GENERAL_TIMEOUT
+        // Arrange.
+        val recorder = createRecorder(
+            sendSurfaceRequest = false,
+            initSourceState = ACTIVE_NON_STREAMING
         )
+        val recording = createRecordingProcess(recorder = recorder)
 
-        // If the recording is paused immediately after being started, the recording should be
-        // finalized with ERROR_NO_VALID_DATA.
-        val captor = ArgumentCaptorCameraX<VideoRecordEvent> { argument ->
-            VideoRecordEvent::class.java.isInstance(
-                argument
-            )
+        // Act.
+        recording.start()
+        recording.pause()
+        recorder.sendSurfaceRequest()
+
+        // Assert.
+        recording.verifyStart()
+        recording.verifyPause()
+        recording.stopAndVerify { finalize ->
+            // Assert.
+            assertThat(finalize.error).isEqualTo(ERROR_NO_VALID_DATA)
         }
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent::class.java,
-            false,
-            CallTimesAtLeast(1),
-            captor
-        )
-
-        val finalize = captor.value as VideoRecordEvent.Finalize
-        assertThat(finalize.error).isEqualTo(ERROR_NO_VALID_DATA)
-
-        file.delete()
     }
 
     @Test
     fun pause_noOpWhenAlreadyPaused() {
-        initializeRecorder()
-        invokeSurfaceRequest()
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
+        // Arrange.
+        val recording = createRecordingProcess()
 
-        recorder.prepareRecording(context, FileOutputOptions.Builder(file).build())
-            .withAudioEnabled()
-            .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer).apply {
-                mockVideoRecordEventConsumer.verifyRecordingStartSuccessfully()
+        // Act.
+        recording.startAndVerify()
+        recording.pauseAndVerify()
+        recording.pause()
 
-                pause()
-
-                mockVideoRecordEventConsumer.verifyAcceptCall(
-                    VideoRecordEvent.Pause::class.java,
-                    true, GENERAL_TIMEOUT
-                )
-
-                pause()
-
-                stopSafely()
-            }
-
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent.Finalize::class.java,
-            true, GENERAL_TIMEOUT
-        )
-
-        // As described in b/197416199, there might be encoded data in flight which will trigger
-        // Status event after pausing. So here it checks there's only one Pause event.
-        val captor = ArgumentCaptorCameraX<VideoRecordEvent> { argument ->
-            VideoRecordEvent::class.java.isInstance(
-                argument
-            )
-        }
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent::class.java,
-            false,
-            CallTimesAtLeast(1),
-            captor
-        )
-
-        assertThat(captor.allValues.count { it is VideoRecordEvent.Pause }).isAtMost(1)
-
-        file.delete()
+        // Assert: One Pause event.
+        val events = recording.getAllEvents()
+        val pauseEvents = events.filterIsInstance<Pause>()
+        assertThat(pauseEvents.size).isAtMost(1)
     }
 
     @Test
     fun pause_throwsExceptionWhenStopping() {
-        initializeRecorder()
-        invokeSurfaceRequest()
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
+        // Arrange.
+        val recording = createRecordingProcess()
 
-        val recording =
-            recorder.prepareRecording(context, FileOutputOptions.Builder(file).build())
-                .withAudioEnabled()
-                .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer)
+        // Act.
+        recording.startAndVerify()
+        recording.stopAndVerify()
 
-        mockVideoRecordEventConsumer.verifyRecordingStartSuccessfully()
-
-        recording.stopSafely()
-
+        // Assert.
         assertThrows(IllegalStateException::class.java) {
             recording.pause()
         }
-
-        file.delete()
     }
 
     @Test
     fun resume_noOpWhenNotPaused() {
-        initializeRecorder()
-        invokeSurfaceRequest()
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
+        // Arrange.
+        val recording = createRecordingProcess()
 
-        val recording =
-            recorder.prepareRecording(context, FileOutputOptions.Builder(file).build())
-                .withAudioEnabled()
-                .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer)
-
-        mockVideoRecordEventConsumer.verifyRecordingStartSuccessfully()
-
-        // Calling resume shouldn't affect the stream of status events finally followed
-        // by a finalize event. There shouldn't be another resume event generated.
+        // Act.
+        recording.startAndVerify()
         recording.resume()
+        recording.stopAndVerify()
 
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent.Status::class.java,
-            true,
-            STATUS_TIMEOUT,
-            CallTimesAtLeast(5)
-        )
-
-        recording.stopSafely()
-
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent.Finalize::class.java,
-            true, GENERAL_TIMEOUT
-        )
-
-        // Ensure no resume events were ever sent.
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent.Resume::class.java,
-            false,
-            GENERAL_TIMEOUT,
-            CallTimes(0)
-        )
-
-        file.delete()
+        // Assert: No Resume event.
+        val events = recording.getAllEvents()
+        val resumeEvents = events.filterIsInstance<Resume>()
+        assertThat(resumeEvents).isEmpty()
     }
 
     @Test
     fun resume_throwsExceptionWhenStopping() {
-        initializeRecorder()
-        invokeSurfaceRequest()
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
+        // Arrange.
+        val recording = createRecordingProcess()
 
-        val recording =
-            recorder.prepareRecording(context, FileOutputOptions.Builder(file).build())
-                .withAudioEnabled()
-                .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer)
+        // Act.
+        recording.startAndVerify()
+        recording.stop()
 
-        mockVideoRecordEventConsumer.verifyRecordingStartSuccessfully()
-
-        recording.stopSafely()
-
+        // Assert.
         assertThrows(IllegalStateException::class.java) {
-            recording.resume()
+            recording.resumeAndVerify()
         }
-
-        file.delete()
     }
 
     @Test
     fun stop_beforeSurfaceRequested() {
-        initializeRecorder()
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
+        // Arrange.
+        val recorder = createRecorder(sendSurfaceRequest = false)
+        val recording = createRecordingProcess(recorder = recorder)
 
-        val recording =
-            recorder.prepareRecording(context, FileOutputOptions.Builder(file).build())
-                .withAudioEnabled()
-                .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer)
+        // Act.
+        recording.start()
+        recording.stop()
+        recorder.sendSurfaceRequest()
 
-        recording.pause()
-
-        recording.stopSafely()
-
-        invokeSurfaceRequest()
-
-        val captor = ArgumentCaptorCameraX<VideoRecordEvent> { argument ->
-            VideoRecordEvent::class.java.isInstance(
-                argument
-            )
+        // Assert.
+        recording.verifyFinalize { finalize ->
+            assertThat(finalize.error).isEqualTo(ERROR_NO_VALID_DATA)
         }
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent::class.java,
-            false,
-            CallTimesAtLeast(1),
-            captor
-        )
-
-        val finalize = captor.value as VideoRecordEvent.Finalize
-        assertThat(finalize.error).isEqualTo(ERROR_NO_VALID_DATA)
-
-        file.delete()
-    }
-
-    @Test
-    fun stop_fromAutoCloseable() {
-        initializeRecorder()
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-
-        // Recording will be stopped by AutoCloseable.close() upon exiting use{} block
-        val pendingRecording =
-            recorder.prepareRecording(context, FileOutputOptions.Builder(file).build())
-        pendingRecording.start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer)
-            .use {
-                invokeSurfaceRequest()
-                mockVideoRecordEventConsumer.verifyRecordingStartSuccessfully()
-            }
-
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent.Finalize::class.java,
-            true, GENERAL_TIMEOUT
-        )
-
-        file.delete()
     }
 
     @Test
     fun stop_WhenUseCaseDetached() {
-        initializeRecorder()
-        invokeSurfaceRequest()
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
+        // Arrange.
+        val recording = createRecordingProcess()
 
-        val recording =
-            recorder.prepareRecording(context, FileOutputOptions.Builder(file).build())
-                .withAudioEnabled()
-                .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer)
-
-        mockVideoRecordEventConsumer.verifyRecordingStartSuccessfully()
-
+        // Act.
+        recording.startAndVerify()
         instrumentation.runOnMainSync {
             cameraUseCaseAdapter.removeUseCases(listOf(preview))
         }
 
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent.Finalize::class.java,
-            true, GENERAL_TIMEOUT
-        )
-
-        recording.stopSafely()
-        file.delete()
+        // Assert.
+        recording.verifyFinalize { finalize ->
+            assertThat(finalize.error).isEqualTo(ERROR_SOURCE_INACTIVE)
+        }
     }
 
-    @Suppress("UNUSED_VALUE", "ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
     @Test
     fun stop_whenRecordingIsGarbageCollected() {
-        initializeRecorder()
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
+        // Arrange.
+        var recording: RecordingProcess? = createRecordingProcess()
+        val listener = recording!!.listener
 
-        var recording: Recording? = recorder
-            .prepareRecording(context, FileOutputOptions.Builder(file).build())
-            .withAudioEnabled()
-            .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer)
-
-        // First ensure the recording gets some status events
-        invokeSurfaceRequest()
-
-        mockVideoRecordEventConsumer.verifyRecordingStartSuccessfully()
-
+        // Act.
+        recording.startAndVerify()
         // Remove reference to recording and run GC. The recording should be stopped once
         // the Recording's finalizer runs.
+        recordingsToStop.remove(recording)
+        @Suppress("UNUSED_VALUE")
         recording = null
         GarbageCollectionUtil.runFinalization()
 
-        // Ensure the event listener gets a finalize event. Note: the word "finalize" is very
-        // overloaded here. This event means the recording has finished, but does not relate to the
-        // finalizer that runs during garbage collection. However, that is what causes the
+        // Assert: Ensure the event listener gets a finalize event. Note: the word "finalize" is
+        // very overloaded here. This event means the recording has finished, but does not relate
+        // to the finalizer that runs during garbage collection. However, that is what causes the
         // recording to finish.
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent.Finalize::class.java,
-            true, GENERAL_TIMEOUT
-        )
-
-        file.delete()
+        listener.verifyFinalize()
     }
 
     @Test
     fun stop_noOpWhenStopping() {
-        initializeRecorder()
-        invokeSurfaceRequest()
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
+        // Arrange.
+        val recording = createRecordingProcess()
 
-        recorder.prepareRecording(context, FileOutputOptions.Builder(file).build())
-            .withAudioEnabled()
-            .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer).apply {
-                mockVideoRecordEventConsumer.verifyRecordingStartSuccessfully()
+        // Act.
+        recording.startAndVerify()
+        recording.stopAndVerify()
+        recording.stop()
 
-                stopSafely()
-                stopSafely()
-            }
-
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent.Finalize::class.java,
-            true, GENERAL_TIMEOUT
-        )
-        mockVideoRecordEventConsumer.verifyNoMoreAcceptCalls(true)
-
-        file.delete()
+        // Assert.
+        recording.verifyNoMoreEvent()
     }
 
     @Test
     fun optionsOverridesDefaults() {
-        initializeRecorder()
         val qualitySelector = QualitySelector.from(Quality.HIGHEST)
-        val recorder = Recorder.Builder()
-            .setQualitySelector(qualitySelector)
-            .build()
+        val recorder = createRecorder(qualitySelector = qualitySelector)
 
         assertThat(recorder.qualitySelector).isEqualTo(qualitySelector)
     }
 
     @Test
     fun canRetrieveProvidedExecutorFromRecorder() {
-        initializeRecorder()
         val myExecutor = Executor { command -> command?.run() }
-        val recorder = Recorder.Builder()
-            .setExecutor(myExecutor)
-            .build()
+        val recorder = createRecorder(executor = myExecutor)
 
         assertThat(recorder.executor).isSameInstanceAs(myExecutor)
     }
 
     @Test
     fun cannotRetrieveExecutorWhenExecutorNotProvided() {
-        initializeRecorder()
-        val recorder = Recorder.Builder().build()
+        val recorder = createRecorder()
 
         assertThat(recorder.executor).isNull()
     }
 
     @Test
     fun canRecordWithoutAudio() {
-        initializeRecorder()
-        invokeSurfaceRequest()
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
+        // Arrange.
+        val recording = createRecordingProcess(withAudio = false)
 
-        val recording =
-            recorder.prepareRecording(context, FileOutputOptions.Builder(file).build())
-                .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer)
-
-        mockVideoRecordEventConsumer.verifyRecordingStartSuccessfully()
-
-        // Check the audio information reports state as disabled.
-        val captor = ArgumentCaptorCameraX<VideoRecordEvent> { argument ->
-            VideoRecordEvent::class.java.isInstance(
-                argument
-            )
+        // Act.
+        recording.startAndVerify()
+        recording.stopAndVerify { finalize ->
+            // Assert.
+            val uri = finalize.outputResults.outputUri
+            checkFileHasAudioAndVideo(uri, hasAudio = false)
         }
-
-        mockVideoRecordEventConsumer.verifyAcceptCall(VideoRecordEvent::class.java,
-            false, CallTimesAtLeast(1), captor)
-
-        assertThat(captor.value).isInstanceOf(VideoRecordEvent.Status::class.java)
-        val status = captor.value as VideoRecordEvent.Status
-        assertThat(status.recordingStats.audioStats.audioState)
-            .isEqualTo(AudioStats.AUDIO_STATE_DISABLED)
-
-        recording.stopSafely()
-
-        mockVideoRecordEventConsumer.verifyAcceptCall(VideoRecordEvent.Finalize::class.java,
-            false, GENERAL_TIMEOUT)
-
-        checkFileAudio(Uri.fromFile(file), false)
-        checkFileVideo(Uri.fromFile(file), true)
-
-        file.delete()
     }
 
     @Test
     fun cannotStartMultiplePendingRecordingsWhileInitializing() {
-        initializeRecorder()
-        val file1 = File.createTempFile("CameraX1", ".tmp").apply { deleteOnExit() }
-        val file2 = File.createTempFile("CameraX2", ".tmp").apply { deleteOnExit() }
-        try {
-            // We explicitly do not invoke the surface request so the recorder is initializing.
-            recorder.prepareRecording(context, FileOutputOptions.Builder(file1).build())
-                .start(CameraXExecutors.directExecutor()) {}
-                .apply {
-                    assertThrows<IllegalStateException> {
-                        recorder.prepareRecording(context, FileOutputOptions.Builder(file2).build())
-                            .start(CameraXExecutors.directExecutor()) {}
-                    }
-                    stopSafely()
-                }
-        } finally {
-            file1.delete()
-            file2.delete()
+        // Arrange: Prepare 1st recording and start.
+        val recorder = createRecorder(sendSurfaceRequest = false)
+        val recording = createRecordingProcess(recorder = recorder)
+        recording.start()
+
+        // Assert.
+        assertThrows<IllegalStateException> {
+            // Act: Prepare 2nd recording and start.
+            createRecordingProcess(recorder = recorder).start()
         }
     }
 
     @Test
     fun canRecoverFromErrorState(): Unit = runBlocking {
-        initializeRecorder()
+        // Arrange.
         // Create a video encoder factory that will fail on first 2 create encoder requests.
-        // Recorder initialization should fail by 1st encoder creation fail.
-        // 1st recording request should fail by 2nd encoder creation fail.
-        // 2nd recording request should be successful.
         var createEncoderRequestCount = 0
-        val recorder = Recorder.Builder()
-            .setVideoEncoderFactory { executor, config ->
-                if (createEncoderRequestCount < 2) {
-                    createEncoderRequestCount++
-                    throw InvalidConfigException("Create video encoder fail on purpose.")
-                } else {
-                    Recorder.DEFAULT_ENCODER_FACTORY.createEncoder(executor, config)
-                }
-            }.build().apply { onSourceStateChanged(VideoOutput.SourceState.INACTIVE) }
-
-        invokeSurfaceRequest(recorder)
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-
+        val recorder = createRecorder(
+            videoEncoderFactory = { executor, config ->
+            if (createEncoderRequestCount < 2) {
+                createEncoderRequestCount++
+                throw InvalidConfigException("Create video encoder fail on purpose.")
+            } else {
+                Recorder.DEFAULT_ENCODER_FACTORY.createEncoder(executor, config)
+            }
+        })
+        // Recorder initialization should fail by 1st encoder creation fail.
         // Wait STREAM_ID_ERROR which indicates Recorder enter the error state.
         withTimeoutOrNull(3000) {
             recorder.streamInfo.asFlow().dropWhile { it!!.id != StreamInfo.STREAM_ID_ERROR }.first()
         } ?: fail("Do not observe STREAM_ID_ERROR from StreamInfo observer.")
 
-        // 1st recording request
-        recorder.prepareRecording(context, FileOutputOptions.Builder(file).build())
-            .withAudioEnabled()
-            .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer).let {
-                val captor = ArgumentCaptorCameraX<VideoRecordEvent> { argument ->
-                    VideoRecordEvent::class.java.isInstance(
-                        argument
-                    )
-                }
+        // Act: 1st recording request should fail by 2nd encoder creation fail.
+        var recording = createRecordingProcess(recorder = recorder)
+        recording.start()
+        recording.verifyFinalize { finalize ->
+            assertThat(finalize.error).isEqualTo(ERROR_RECORDER_ERROR)
+        }
 
-                mockVideoRecordEventConsumer.verifyAcceptCall(
-                    VideoRecordEvent::class.java,
-                    false,
-                    3000L,
-                    CallTimesAtLeast(1),
-                    captor
-                )
-
-                val finalize = captor.value as VideoRecordEvent.Finalize
-                assertThat(finalize.error).isEqualTo(ERROR_RECORDER_ERROR)
-            }
-
-        // 2nd recording request
-        mockVideoRecordEventConsumer.clearAcceptCalls()
-        recorder.prepareRecording(context, FileOutputOptions.Builder(file).build())
-            .withAudioEnabled()
-            .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer).let {
-                mockVideoRecordEventConsumer.verifyRecordingStartSuccessfully()
-
-                it.stopSafely()
-
-                mockVideoRecordEventConsumer.verifyAcceptCall(
-                    VideoRecordEvent.Finalize::class.java,
-                    true, GENERAL_TIMEOUT
-                )
-            }
-
-        file.delete()
+        // Act: 2nd recording request should be successful.
+        recording = createRecordingProcess(recorder = recorder)
+        recording.startAndVerify()
+        recording.stopAndVerify()
     }
 
     @Test
     @SdkSuppress(minSdkVersion = 31)
     fun audioRecordIsAttributed() = runBlocking {
-        initializeRecorder()
+        // Arrange.
         val notedTag = CompletableDeferred<String>()
         val appOps = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
-        appOps.setOnOpNotedCallback(
-            Dispatchers.Main.asExecutor(),
-            object : AppOpsManager.OnOpNotedCallback() {
-                override fun onNoted(p0: SyncNotedAppOp) {
-                    // no-op. record_audio should be async.
-                }
+        appOps.setOnOpNotedCallback(Dispatchers.Main.asExecutor(), object : OnOpNotedCallback() {
+            override fun onNoted(p0: SyncNotedAppOp) {
+                // no-op. record_audio should be async.
+            }
 
-                override fun onSelfNoted(p0: SyncNotedAppOp) {
-                    // no-op. record_audio should be async.
-                }
+            override fun onSelfNoted(p0: SyncNotedAppOp) {
+                // no-op. record_audio should be async.
+            }
 
-                override fun onAsyncNoted(noted: AsyncNotedAppOp) {
-                    if (AppOpsManager.OPSTR_RECORD_AUDIO == noted.op &&
-                        TEST_ATTRIBUTION_TAG == noted.attributionTag
-                    ) {
-                        notedTag.complete(noted.attributionTag!!)
-                    }
+            override fun onAsyncNoted(noted: AsyncNotedAppOp) {
+                if (AppOpsManager.OPSTR_RECORD_AUDIO == noted.op &&
+                    TEST_ATTRIBUTION_TAG == noted.attributionTag
+                ) {
+                    notedTag.complete(noted.attributionTag!!)
                 }
-            })
+            }
+        })
+        val attributionContext = context.createAttributionContext(TEST_ATTRIBUTION_TAG)
+        val recording = createRecordingProcess(context = attributionContext)
 
-        var recording: Recording? = null
+        // Act.
+        recording.start()
         try {
-            val attributionContext = context.createAttributionContext(TEST_ATTRIBUTION_TAG)
-            invokeSurfaceRequest()
-            val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-
-            recording =
-                recorder.prepareRecording(
-                    attributionContext, FileOutputOptions.Builder(file).build()
-                )
-                    .withAudioEnabled()
-                    .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer)
-
             val timeoutDuration = 5.seconds
             withTimeoutOrNull(timeoutDuration) {
+                // Assert.
                 assertThat(notedTag.await()).isEqualTo(TEST_ATTRIBUTION_TAG)
             } ?: fail("Timed out waiting for attribution tag. Waited $timeoutDuration.")
         } finally {
             appOps.setOnOpNotedCallback(null, null)
-            recording?.stopSafely()
         }
     }
 
-    private fun invokeSurfaceRequest() {
-        invokeSurfaceRequest(recorder)
-    }
+    private fun testRecorderIsConfiguredBasedOnTargetVideoEncodingBitrate(targetBitrate: Int) {
+        // Arrange.
+        val recorder = createRecorder(targetBitrate = targetBitrate)
+        val recording = createRecordingProcess(recorder = recorder, withAudio = false)
 
-    private fun invokeSurfaceRequest(recorder: Recorder) {
-        instrumentation.runOnMainSync {
-            preview.setSurfaceProvider { request: SurfaceRequest ->
-                recorder.onSurfaceRequested(request)
-            }
-            recorder.onSourceStateChanged(VideoOutput.SourceState.ACTIVE_STREAMING)
-        }
-    }
-
-    private fun initializeRecorder(bitrate: Int = BITRATE_AUTO) {
-        recorder = Recorder.Builder().apply {
-            if (bitrate != BITRATE_AUTO) {
-                setTargetVideoEncodingBitRate(bitrate)
-            }
-        }.build()
-        recorder.onSourceStateChanged(VideoOutput.SourceState.ACTIVE_NON_STREAMING)
-    }
-
-    private fun testRecorderIsConfiguredBasedOnTargetVideoEncodingBitrate(
-        bitrate: Int,
-        enableAudio: Boolean = false
-    ) {
-        initializeRecorder(bitrate)
-        invokeSurfaceRequest()
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-
-        val recording =
-            recorder.prepareRecording(context, FileOutputOptions.Builder(file).build())
-                .apply { if (enableAudio) withAudioEnabled() }
-                .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer)
-
-        mockVideoRecordEventConsumer.verifyRecordingStartSuccessfully()
-
-        recording.stopSafely()
-
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent.Finalize::class.java,
-            true,
-            GENERAL_TIMEOUT
-        )
-
-        val uri = Uri.fromFile(file)
-        if (enableAudio) {
-            checkFileHasAudioAndVideo(uri)
-        } else {
-            checkFileVideo(uri, true)
+        // Act.
+        recording.startAndVerify()
+        recording.stopAndVerify { finalize ->
+            assertThat(finalize.error).isEqualTo(ERROR_NONE)
         }
 
-        // Check the output Uri from the finalize event match the Uri from the given file.
-        val captor = ArgumentCaptorCameraX<VideoRecordEvent> { argument ->
-            VideoRecordEvent::class.java.isInstance(
-                argument
-            )
-        }
-
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent::class.java,
-            false,
-            CallTimesAtLeast(1),
-            captor
-        )
-
-        val finalize = captor.value as VideoRecordEvent.Finalize
-        assertThat(finalize.outputResults.outputUri).isEqualTo(uri)
-
-        file.delete()
-    }
-
-    private fun verifyConfiguredVideoBitrate() {
+        // Assert.
         assertThat(recorder.mFirstRecordingVideoBitrate).isIn(
             com.google.common.collect.Range.closed(
                 recorder.mVideoEncoderBitrateRange.lower,
@@ -1388,96 +919,224 @@
         )
     }
 
-    private fun checkFileHasAudioAndVideo(uri: Uri) {
-        checkFileAudio(uri, true)
-        checkFileVideo(uri, true)
-    }
-
-    private fun checkFileAudio(uri: Uri, hasAudio: Boolean) {
-        MediaMetadataRetriever().apply {
-            try {
-                setDataSource(context, uri)
-                val value = extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO)
-
-                assertThat(value).isEqualTo(
-                    if (hasAudio) {
-                        "yes"
-                    } else {
-                        null
-                    }
-                )
-            } finally {
-                release()
+    private fun Recorder.sendSurfaceRequest() {
+        instrumentation.runOnMainSync {
+            preview.setSurfaceProvider { request: SurfaceRequest ->
+                onSurfaceRequested(request)
             }
         }
     }
 
-    private fun checkFileVideo(uri: Uri, hasVideo: Boolean) {
-        MediaMetadataRetriever().apply {
-            try {
-                setDataSource(context, uri)
-                val value = extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO)
+    private fun createTempFile() = temporaryFolder.newFile()
 
-                assertThat(value).isEqualTo(
-                    if (hasVideo) {
-                        "yes"
-                    } else {
-                        null
-                    }
-                )
-            } finally {
-                release()
+    private fun createRecorder(
+        sendSurfaceRequest: Boolean = true,
+        initSourceState: VideoOutput.SourceState = ACTIVE_STREAMING,
+        qualitySelector: QualitySelector? = null,
+        executor: Executor? = null,
+        videoEncoderFactory: EncoderFactory? = null,
+        audioEncoderFactory: EncoderFactory? = null,
+        targetBitrate: Int? = null,
+    ): Recorder {
+        val recorder = Recorder.Builder().apply {
+            qualitySelector?.let { setQualitySelector(it) }
+            executor?.let { setExecutor(it) }
+            videoEncoderFactory?.let { setVideoEncoderFactory(it) }
+            audioEncoderFactory?.let { setAudioEncoderFactory(it) }
+            targetBitrate?.let { setTargetVideoEncodingBitRate(targetBitrate) }
+        }.build()
+        if (sendSurfaceRequest) {
+            recorder.sendSurfaceRequest()
+        }
+        recorder.onSourceStateChanged(initSourceState)
+        return recorder
+    }
+
+    private fun createFileOutputOptions(
+        file: File = createTempFile(),
+        fileSizeLimit: Long? = null,
+        durationLimitMillis: Long? = null,
+        location: Location? = null,
+    ): FileOutputOptions = FileOutputOptions.Builder(file).apply {
+        fileSizeLimit?.let { setFileSizeLimit(it) }
+        durationLimitMillis?.let { setDurationLimitMillis(it) }
+        location?.let { setLocation(it) }
+    }.build()
+
+    private fun createRecordingProcess(
+        recorder: Recorder = createRecorder(),
+        context: Context = ApplicationProvider.getApplicationContext(),
+        outputOptions: OutputOptions = createFileOutputOptions(),
+        withAudio: Boolean = true
+    ) = RecordingProcess(
+        recorder,
+        context,
+        outputOptions,
+        withAudio
+    )
+
+    inner class RecordingProcess(
+        private val recorder: Recorder,
+        context: Context,
+        outputOptions: OutputOptions,
+        withAudio: Boolean
+    ) {
+        private val pendingRecording: PendingRecording =
+            PendingRecording(context, recorder, outputOptions).apply {
+                if (withAudio) {
+                    withAudioEnabled()
+                }
             }
+        val listener = MockConsumer<VideoRecordEvent>()
+        private lateinit var recording: Recording
+
+        fun startAndVerify(
+            statusCount: Int = DEFAULT_STATUS_COUNT,
+            onStatus: ((List<Status>) -> Unit)? = null,
+        ) = startInternal(verify = true, statusCount = statusCount, onStatus = onStatus)
+
+        fun start() = startInternal(verify = false)
+
+        private fun startInternal(
+            verify: Boolean = false,
+            statusCount: Int = DEFAULT_STATUS_COUNT,
+            onStatus: ((List<Status>) -> Unit)? = null
+        ) {
+            recording = pendingRecording.start(mainThreadExecutor(), listener)
+            recordingsToStop.add(this)
+            if (verify) {
+                verifyStart()
+                verifyStatus(statusCount = statusCount, onStatus = onStatus)
+            }
+        }
+
+        fun verifyStart() {
+            listener.verifyStart()
+        }
+
+        fun verifyStatus(
+            statusCount: Int = DEFAULT_STATUS_COUNT,
+            onStatus: ((List<Status>) -> Unit)? = null,
+        ) {
+            listener.verifyStatus(eventCount = statusCount, onEvent = onStatus)
+        }
+
+        fun stopAndVerify(onFinalize: ((Finalize) -> Unit)? = null) =
+            stopInternal(verify = true, onFinalize)
+
+        fun stop() = stopInternal(verify = false)
+
+        private fun stopInternal(
+            verify: Boolean = false,
+            onFinalize: ((Finalize) -> Unit)? = null
+        ) {
+            recording.stopSafely(recorder)
+            if (verify) {
+                verifyFinalize(onFinalize = onFinalize)
+            }
+        }
+
+        fun verifyFinalize(
+            timeoutMs: Long = GENERAL_TIMEOUT,
+            onFinalize: ((Finalize) -> Unit)? = null
+        ) = listener.verifyFinalize(timeoutMs = timeoutMs, onFinalize = onFinalize)
+
+        fun pauseAndVerify() = pauseInternal(verify = true)
+
+        fun pause() = pauseInternal(verify = false)
+
+        private fun pauseInternal(verify: Boolean = false) {
+            recording.pause()
+            if (verify) {
+                verifyPause()
+            }
+        }
+
+        fun verifyPause() = listener.verifyPause()
+
+        fun resumeAndVerify() = resumeInternal(verify = true)
+
+        fun resume() = resumeInternal(verify = false)
+
+        private fun resumeInternal(verify: Boolean = false) {
+            recording.resume()
+            if (verify) {
+                verifyResume()
+            }
+        }
+
+        private fun verifyResume() {
+            listener.verifyResume()
+            listener.verifyStatus()
+        }
+
+        fun getAllEvents(): List<VideoRecordEvent> {
+            lateinit var events: List<VideoRecordEvent>
+            listener.verifyEvent(
+                VideoRecordEvent::class.java,
+                CallTimesAtLeast(1),
+                onEvent = {
+                    events = it
+                }
+            )
+            return events
+        }
+
+        fun verifyNoMoreEvent() = listener.verifyNoMoreAcceptCalls(/*inOrder=*/true)
+    }
+
+    private fun checkFileHasAudioAndVideo(
+        uri: Uri,
+        hasAudio: Boolean = true,
+    ) {
+        MediaMetadataRetriever().useAndRelease {
+            it.setDataSource(context, uri)
+            assertThat(it.hasVideo()).isEqualTo(true)
+            assertThat(it.hasAudio()).isEqualTo(hasAudio)
         }
     }
 
+    @Suppress("SameParameterValue")
     private fun checkLocation(uri: Uri, location: Location) {
-        MediaMetadataRetriever().apply {
-            try {
-                setDataSource(context, uri)
-                // Only test on mp4 output format, others will be ignored.
-                val mime = extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE)
-                assumeTrue("Unsupported mime = $mime",
-                    "video/mp4".equals(mime, ignoreCase = true))
-                val value = extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION)
-                assertThat(value).isNotNull()
-                // ex: (90, 180) => "+90.0000+180.0000/" (ISO-6709 standard)
-                val matchGroup =
-                    "([\\+-]?[0-9]+(\\.[0-9]+)?)([\\+-]?[0-9]+(\\.[0-9]+)?)".toRegex()
-                        .find(value!!) ?: fail("Fail on checking location metadata: $value")
-                val lat = matchGroup.groupValues[1].toDouble()
-                val lon = matchGroup.groupValues[3].toDouble()
+        MediaMetadataRetriever().useAndRelease {
+            it.setDataSource(context, uri)
+            // Only test on mp4 output format, others will be ignored.
+            val mime = it.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE)
+            assumeTrue("Unsupported mime = $mime",
+                "video/mp4".equals(mime, ignoreCase = true))
+            val value = it.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION)
+            assertThat(value).isNotNull()
+            // ex: (90, 180) => "+90.0000+180.0000/" (ISO-6709 standard)
+            val matchGroup =
+                "([+-]?[0-9]+(\\.[0-9]+)?)([+-]?[0-9]+(\\.[0-9]+)?)".toRegex()
+                    .find(value!!) ?: fail("Fail on checking location metadata: $value")
+            val lat = matchGroup.groupValues[1].toDouble()
+            val lon = matchGroup.groupValues[3].toDouble()
 
-                // MediaMuxer.setLocation rounds the value to 4 decimal places
-                val tolerance = 0.0001
-                assertWithMessage("Fail on latitude. $lat($value) vs ${location.latitude}")
-                    .that(lat).isWithin(tolerance).of(location.latitude)
-                assertWithMessage("Fail on longitude. $lon($value) vs ${location.longitude}")
-                    .that(lon).isWithin(tolerance).of(location.longitude)
-            } finally {
-                release()
-            }
+            // MediaMuxer.setLocation rounds the value to 4 decimal places
+            val tolerance = 0.0001
+            assertWithMessage("Fail on latitude. $lat($value) vs ${location.latitude}")
+                .that(lat).isWithin(tolerance).of(location.latitude)
+            assertWithMessage("Fail on longitude. $lon($value) vs ${location.longitude}")
+                .that(lon).isWithin(tolerance).of(location.longitude)
         }
     }
 
+    @Suppress("SameParameterValue")
     private fun checkDurationAtMost(uri: Uri, duration: Long) {
-        MediaMetadataRetriever().apply {
-            try {
-                setDataSource(context, uri)
-                val durationFromFile = extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
+        MediaMetadataRetriever().useAndRelease {
+            it.setDataSource(context, uri)
+            val durationFromFile = it.getDuration()
 
-                assertThat(durationFromFile).isNotNull()
-                assertThat(durationFromFile!!.toLong()).isAtMost(duration)
-            } finally {
-                release()
-            }
+            assertThat(durationFromFile).isNotNull()
+            assertThat(durationFromFile!!).isAtMost(duration)
         }
     }
 
     // It fails on devices with certain chipset if the codec is stopped when the camera is still
     // producing frames to the provided surface. This method first stop the camera from
     // producing frames then stops the recording safely on the problematic devices.
-    private fun Recording.stopSafely() {
+    private fun Recording.stopSafely(recorder: Recorder) {
         val deactivateSurfaceBeforeStop =
             DeviceQuirks.get(DeactivateEncoderSurfaceBeforeStopEncoderQuirk::class.java) != null
         if (deactivateSurfaceBeforeStop) {
@@ -1487,70 +1146,21 @@
         }
         stop()
         if (deactivateSurfaceBeforeStop && Build.VERSION.SDK_INT >= 23) {
-            invokeSurfaceRequest()
+            recorder.sendSurfaceRequest()
         }
     }
 
-    private fun runFileSizeLimitTest(fileSizeLimit: Long) {
-        // For the file size is small, the final file length possibly exceeds the file size limit
-        // after adding the file header. We still add the buffer for the tolerance of comparing the
-        // file length and file size limit.
-        val sizeLimitBuffer = 50 * 1024 // 50k threshold buffer
-        invokeSurfaceRequest()
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.Builder(file)
-            .setFileSizeLimit(fileSizeLimit)
-            .build()
-
-        val recording = recorder
-            .prepareRecording(context, outputOptions)
-            .withAudioEnabled()
-            .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer)
-
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent.Finalize::class.java,
-            false, 60000L
-        )
-
-        val captor = ArgumentCaptorCameraX<VideoRecordEvent> {
-                argument -> VideoRecordEvent::class.java.isInstance(argument)
-        }
-        mockVideoRecordEventConsumer.verifyAcceptCall(VideoRecordEvent::class.java,
-            false, CallTimesAtLeast(1), captor)
-
-        assertThat(captor.value).isInstanceOf(VideoRecordEvent.Finalize::class.java)
-        val finalize = captor.value as VideoRecordEvent.Finalize
-        assertThat(finalize.error).isEqualTo(ERROR_FILE_SIZE_LIMIT_REACHED)
-        assertThat(file.length()).isLessThan(fileSizeLimit + sizeLimitBuffer)
-
-        recording.stopSafely()
-
-        file.delete()
-    }
-
     private fun runLocationTest(location: Location) {
-        invokeSurfaceRequest(recorder)
-        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
-        val outputOptions = FileOutputOptions.Builder(file)
-            .setLocation(location)
-            .build()
+        // Arrange.
+        val outputOptions = createFileOutputOptions(location = location)
+        val recording = createRecordingProcess(outputOptions = outputOptions)
 
-        val recording = recorder
-            .prepareRecording(context, outputOptions)
-            .start(CameraXExecutors.directExecutor(), mockVideoRecordEventConsumer)
-
-        mockVideoRecordEventConsumer.verifyRecordingStartSuccessfully()
-
-        recording.stopSafely()
-
-        mockVideoRecordEventConsumer.verifyAcceptCall(
-            VideoRecordEvent.Finalize::class.java,
-            true, GENERAL_TIMEOUT
-        )
-
-        checkLocation(Uri.fromFile(file), location)
-
-        file.delete()
+        // Act.
+        recording.startAndVerify()
+        recording.stopAndVerify { finalize ->
+            // Assert.
+            checkLocation(finalize.outputResults.outputUri, location)
+        }
     }
 
     private fun createLocation(
@@ -1563,50 +1173,85 @@
             this.longitude = longitude
         }
 
-    private fun MockConsumer<VideoRecordEvent>.verifyRecordingStartSuccessfully() {
-        verifyAcceptCall(
-            VideoRecordEvent.Start::class.java,
-            true,
-            GENERAL_TIMEOUT
-        )
-        verifyAcceptCall(
-            VideoRecordEvent.Status::class.java,
-            true,
-            STATUS_TIMEOUT,
-            CallTimesAtLeast(5)
+    private fun MockConsumer<VideoRecordEvent>.verifyStart(
+        inOrder: Boolean = true,
+        onEvent: ((Start) -> Unit)? = null
+    ) {
+        verifyEvent(Start::class.java, inOrder = inOrder, onEvent = onEvent)
+    }
+
+    private fun MockConsumer<VideoRecordEvent>.verifyFinalize(
+        inOrder: Boolean = true,
+        timeoutMs: Long = GENERAL_TIMEOUT,
+        onFinalize: ((Finalize) -> Unit)? = null
+    ) {
+        verifyEvent(
+            Finalize::class.java,
+            inOrder = inOrder,
+            timeoutMs = timeoutMs,
+            onEvent = onFinalize
         )
     }
-    class VideoCaptureMonitor : Consumer<VideoRecordEvent> {
-        private var countDown: CountDownLatch? = null
 
-        fun waitForVideoCaptureStatus(
-            count: Int = 10,
-            timeoutMillis: Long = TimeUnit.SECONDS.toMillis(10)
-        ) {
-            assertWithMessage("Video recording doesn't start").that(synchronized(this) {
-                countDown = CountDownLatch(count)
-                countDown
-            }!!.await(timeoutMillis, TimeUnit.MILLISECONDS)).isTrue()
+    private fun MockConsumer<VideoRecordEvent>.verifyStatus(
+        eventCount: Int = DEFAULT_STATUS_COUNT,
+        inOrder: Boolean = true,
+        onEvent: ((List<Status>) -> Unit)? = null,
+    ) {
+        verifyEvent(
+            Status::class.java,
+            CallTimesAtLeast(eventCount),
+            inOrder = inOrder,
+            timeoutMs = STATUS_TIMEOUT,
+            onEvent = onEvent
+        )
+    }
+
+    private fun MockConsumer<VideoRecordEvent>.verifyPause(
+        inOrder: Boolean = true,
+        onEvent: ((Pause) -> Unit)? = null
+    ) {
+        verifyEvent(Pause::class.java, inOrder = inOrder, onEvent = onEvent)
+    }
+
+    private fun MockConsumer<VideoRecordEvent>.verifyResume(
+        inOrder: Boolean = true,
+        onEvent: ((Resume) -> Unit)? = null,
+    ) {
+        verifyEvent(Resume::class.java, inOrder = inOrder, onEvent = onEvent)
+    }
+
+    private fun <T : VideoRecordEvent> MockConsumer<VideoRecordEvent>.verifyEvent(
+        eventType: Class<T>,
+        inOrder: Boolean = false,
+        timeoutMs: Long = GENERAL_TIMEOUT,
+        onEvent: ((T) -> Unit)? = null,
+    ) {
+        verifyEvent(
+            eventType,
+            callTimes = CallTimes(1),
+            inOrder = inOrder,
+            timeoutMs = timeoutMs
+        ) { events ->
+            onEvent?.invoke(events.last())
         }
+    }
 
-        override fun accept(event: VideoRecordEvent?) {
-            when (event) {
-                is VideoRecordEvent.Status -> {
-                    synchronized(this) {
-                        countDown?.countDown()
-                    }
-                }
-                else -> {
-                    // Ignore other events.
-                }
+    private fun <T : VideoRecordEvent> MockConsumer<VideoRecordEvent>.verifyEvent(
+        eventType: Class<T>,
+        callTimes: CallTimes,
+        inOrder: Boolean = false,
+        timeoutMs: Long = GENERAL_TIMEOUT,
+        onEvent: ((List<T>) -> Unit)? = null,
+    ) {
+        verifyAcceptCall(eventType, inOrder, timeoutMs, callTimes)
+        if (onEvent != null) {
+            val captor = ArgumentCaptorCameraX<VideoRecordEvent> { argument ->
+                eventType.isInstance(argument)
             }
+            verifyAcceptCall(eventType, false, callTimes, captor)
+            @Suppress("UNCHECKED_CAST")
+            onEvent.invoke(captor.allValues as List<T>)
         }
     }
-
-    private fun Recorder.startVideoRecording(
-        file: File,
-        eventListener: Consumer<VideoRecordEvent>
-    ): Recording = prepareRecording(
-        context, FileOutputOptions.Builder(file).build()
-    ).start(Dispatchers.Main.asExecutor(), eventListener)
 }
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
index 3b00ca9..912c57a 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
@@ -448,7 +448,7 @@
         instrumentation.runOnMainSync {
             cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview, videoCapture)
         }
-        val videoCaptureMonitor = RecorderTest.VideoCaptureMonitor()
+        val videoCaptureMonitor = VideoCaptureMonitor()
         videoCapture.startVideoRecording(temporaryFolder.newFile(), videoCaptureMonitor).use {
             // Ensure the Recorder is initialized before start test.
             videoCaptureMonitor.waitForVideoCaptureStatus()
@@ -1080,10 +1080,46 @@
         )
 }
 
-private fun MediaMetadataRetriever.useAndRelease(block: (MediaMetadataRetriever) -> Unit) {
+private class VideoCaptureMonitor : Consumer<VideoRecordEvent> {
+    private var countDown: CountDownLatch? = null
+
+    fun waitForVideoCaptureStatus(
+        count: Int = 10,
+        timeoutMillis: Long = TimeUnit.SECONDS.toMillis(10)
+    ) {
+        assertWithMessage("Video recording doesn't start").that(synchronized(this) {
+            countDown = CountDownLatch(count)
+            countDown
+        }!!.await(timeoutMillis, TimeUnit.MILLISECONDS)).isTrue()
+    }
+
+    override fun accept(event: VideoRecordEvent?) {
+        when (event) {
+            is VideoRecordEvent.Status -> {
+                synchronized(this) {
+                    countDown?.countDown()
+                }
+            }
+            else -> {
+                // Ignore other events.
+            }
+        }
+    }
+}
+
+internal fun MediaMetadataRetriever.useAndRelease(block: (MediaMetadataRetriever) -> Unit) {
     try {
         block(this)
     } finally {
         release()
     }
 }
+
+internal fun MediaMetadataRetriever.hasAudio(): Boolean =
+    extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO) == "yes"
+
+internal fun MediaMetadataRetriever.hasVideo(): Boolean =
+    extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO) == "yes"
+
+internal fun MediaMetadataRetriever.getDuration(): Long? =
+    extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong()
\ No newline at end of file
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt
index 5f5ee52..1c2f921 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/VideoCaptureDeviceTest.kt
@@ -433,7 +433,7 @@
     }
 
     @Test
-    @SdkSuppress(maxSdkVersion = 33) // b/262909049: Failing on SDK 34
+    @SdkSuppress(minSdkVersion = 21, maxSdkVersion = 33) // b/262909049: Failing on SDK 34
     fun canRecordToFile_whenPauseAndResumeInTheMiddle() {
         val pauseTimes = 1
         val resumeTimes = 1
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXInitTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXInitTest.kt
index 0d4c7e8..d8b0f40 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXInitTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/CameraXInitTest.kt
@@ -23,6 +23,7 @@
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.CameraXConfig
 import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.testing.LabTestRule
 import androidx.concurrent.futures.await
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.LargeTest
@@ -58,6 +59,9 @@
     val permissionRule: GrantPermissionRule =
         GrantPermissionRule.grant(android.Manifest.permission.CAMERA)
 
+    @get:Rule
+    val labTest: LabTestRule = LabTestRule()
+
     private val context = ApplicationProvider.getApplicationContext<Context>()
     private val packageManager = context.packageManager
     private lateinit var cameraProvider: ProcessCameraProvider
@@ -88,6 +92,9 @@
         }
     }
 
+    // Only test on lab devices because emulator may not have correctly set the matching camera
+    // features and camera list.
+    @LabTestRule.LabTestOnly
     @Test
     fun initOnDevice_hasCamera() {
         ProcessCameraProvider.configureInstance(cameraXConfig)
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FocusMeteringDeviceTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FocusMeteringDeviceTest.kt
index b7c5042..a428460 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FocusMeteringDeviceTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FocusMeteringDeviceTest.kt
@@ -51,7 +51,6 @@
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
 import org.hamcrest.CoreMatchers.equalTo
-import org.hamcrest.CoreMatchers.not
 import org.junit.After
 import org.junit.Assume
 import org.junit.Assume.assumeThat
@@ -262,11 +261,6 @@
 
     @Test
     fun focusMeteringFailsWithIllegalArgumentException_whenMeteringPointInvalid() = runBlocking {
-        assumeThat(
-            "Test ignored for CameraPipe config: b/263323720",
-            implName, not(CameraPipeConfig::class.simpleName)
-        )
-
         val focusMeteringAction = FocusMeteringAction.Builder(invalidMeteringPoint).build()
 
         assumeThat(
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/OpenCloseCaptureSessionStressTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/OpenCloseCaptureSessionStressTest.kt
index 0edd0a0..925d785 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/OpenCloseCaptureSessionStressTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/OpenCloseCaptureSessionStressTest.kt
@@ -17,27 +17,17 @@
 package androidx.camera.integration.extensions
 
 import android.content.Context
+import android.hardware.camera2.CameraCaptureSession
+import android.hardware.camera2.CameraDevice
+import android.view.Surface
 import androidx.camera.camera2.Camera2Config
-import androidx.camera.camera2.impl.Camera2ImplConfig
-import androidx.camera.camera2.impl.CameraEventCallback
-import androidx.camera.camera2.impl.CameraEventCallbacks
-import androidx.camera.camera2.interop.Camera2CameraInfo
+import androidx.camera.camera2.interop.Camera2Interop
 import androidx.camera.core.Camera
-import androidx.camera.core.CameraFilter
-import androidx.camera.core.CameraInfo
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.ImageAnalysis
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.Preview
 import androidx.camera.core.UseCase
-import androidx.camera.core.impl.CameraConfig
-import androidx.camera.core.impl.CaptureConfig
-import androidx.camera.core.impl.Config
-import androidx.camera.core.impl.ExtendedCameraConfigProviderStore
-import androidx.camera.core.impl.Identifier
-import androidx.camera.core.impl.MutableOptionsBundle
-import androidx.camera.core.impl.OptionsBundle
-import androidx.camera.core.impl.UseCaseConfigFactory
 import androidx.camera.extensions.ExtensionMode
 import androidx.camera.extensions.ExtensionsManager
 import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil
@@ -88,7 +78,7 @@
     private lateinit var imageAnalysis: ImageAnalysis
     private var isImageAnalysisSupported = false
     private lateinit var lifecycleOwner: FakeLifecycleOwner
-    private val cameraEventMonitor = CameraEventMonitor()
+    private val cameraSessionMonitor = CameraSessionMonitor()
 
     @Before
     fun setUp(): Unit = runBlocking {
@@ -114,7 +104,9 @@
             cameraProvider.bindToLifecycle(lifecycleOwner, extensionCameraSelector)
         }
 
-        preview = Preview.Builder().build()
+        val previewBuilder = Preview.Builder()
+        injectCameraSessionMonitor(previewBuilder, cameraSessionMonitor)
+        preview = previewBuilder.build()
         withContext(Dispatchers.Main) {
             preview.setSurfaceProvider(SurfaceTextureProvider.createSurfaceTextureProvider())
         }
@@ -125,6 +117,51 @@
             camera.isUseCasesCombinationSupported(preview, imageCapture, imageAnalysis)
     }
 
+    private fun injectCameraSessionMonitor(
+        previewBuilder: Preview.Builder,
+        cameraMonitor: CameraSessionMonitor
+    ) {
+        Camera2Interop.Extender(previewBuilder)
+            .setSessionStateCallback(object : CameraCaptureSession.StateCallback() {
+                override fun onConfigured(session: CameraCaptureSession) {
+                    cameraMonitor.onOpenedSession()
+                }
+
+                override fun onClosed(session: CameraCaptureSession) {
+                    cameraMonitor.onClosedSession()
+                }
+
+                override fun onConfigureFailed(session: CameraCaptureSession) {
+                }
+
+                override fun onReady(session: CameraCaptureSession) {
+                }
+
+                override fun onActive(session: CameraCaptureSession) {
+                }
+
+                override fun onCaptureQueueEmpty(session: CameraCaptureSession) {
+                }
+
+                override fun onSurfacePrepared(session: CameraCaptureSession, surface: Surface) {
+                }
+            })
+            .setDeviceStateCallback(object : CameraDevice.StateCallback() {
+                override fun onOpened(device: CameraDevice) {
+                }
+                // Some device doesn't invoke CameraCaptureSession onClosed callback thus
+                // we need to invoke when camera is closed.
+                override fun onClosed(device: CameraDevice) {
+                    cameraMonitor.onClosedSession()
+                }
+
+                override fun onDisconnected(device: CameraDevice) {
+                }
+
+                override fun onError(device: CameraDevice, error: Int) {
+                }
+            })
+    }
     @After
     fun cleanUp(): Unit = runBlocking {
         if (::cameraProvider.isInitialized) {
@@ -157,7 +194,7 @@
 
     /**
      * Repeatedly binds use cases, unbind all to check whether the capture session can be opened
-     * and closed successfully by monitoring the CameraEvent callbacks.
+     * and closed successfully by monitoring the camera session state.
      */
     private fun bindUseCase_unbindAll_toCheckCameraEvent_repeatedly(
         vararg useCases: UseCase,
@@ -165,36 +202,27 @@
     ): Unit = runBlocking {
         for (i in 1..repeatCount) {
             // Arrange: resets the camera event monitor
-            cameraEventMonitor.reset()
+            cameraSessionMonitor.reset()
 
             withContext(Dispatchers.Main) {
-                // Arrange: retrieves the camera selector which allows to monitor camera event
-                // callbacks
-                val extensionEnabledCameraEventMonitorCameraSelector =
-                    getExtensionsCameraEventMonitorCameraSelector(
-                        extensionsManager,
-                        config.extensionMode,
-                        baseCameraSelector
-                    )
-
                 // Act: binds use cases
                 cameraProvider.bindToLifecycle(
                     lifecycleOwner,
-                    extensionEnabledCameraEventMonitorCameraSelector,
+                    extensionCameraSelector,
                     *useCases
                 )
             }
 
-            // Assert: checks the CameraEvent#onEnableSession callback function is called
-            cameraEventMonitor.awaitSessionEnabledAndAssert()
+            // Assert: checks the camera session is opened.
+            cameraSessionMonitor.awaitSessionOpenedAndAssert()
 
             // Act: unbinds all use cases
             withContext(Dispatchers.Main) {
                 cameraProvider.unbindAll()
             }
 
-            // Assert: checks the CameraEvent#onSessionDisabled callback function is called
-            cameraEventMonitor.awaitSessionDisabledAndAssert()
+            // Assert: checks the camera session is closed.
+            cameraSessionMonitor.awaitSessionClosedAndAssert()
         }
     }
 
@@ -231,186 +259,18 @@
     }
 
     /**
-     * Gets the camera selector which allows to monitor the camera event callbacks
+     * An implementation of CameraEventCallback to monitor whether the camera is closed or opened.
      */
-    private fun getExtensionsCameraEventMonitorCameraSelector(
-        extensionsManager: ExtensionsManager,
-        extensionMode: Int,
-        baseCameraSelector: CameraSelector
-    ): CameraSelector {
-        // Injects the ExtensionsCameraEventMonitorUseCaseConfigFactory which allows to monitor and
-        // verify the camera event callbacks
-        injectExtensionsCameraEventMonitorUseCaseConfigFactory(
-            extensionsManager,
-            extensionMode,
-            baseCameraSelector
-        )
-
-        val builder = CameraSelector.Builder.fromSelector(baseCameraSelector)
-        // Add an ExtensionCameraEventMonitorCameraFilter which includes the CameraFilter to check
-        // whether the camera is supported for the extension mode or not and also includes the
-        // identifier to find the extended camera config provider from
-        // ExtendedCameraConfigProviderStore
-        builder.addCameraFilter(
-            ExtensionsCameraEventMonitorCameraFilter(
-                extensionsManager,
-                extensionMode
-            )
-        )
-        return builder.build()
-    }
-
-    /**
-     * Injects the ExtensionsCameraEventMonitorUseCaseConfigFactory which allows to monitor and
-     * verify the camera event callbacks
-     */
-    private fun injectExtensionsCameraEventMonitorUseCaseConfigFactory(
-        extensionsManager: ExtensionsManager,
-        extensionMode: Int,
-        baseCameraSelector: CameraSelector
-    ): Unit = runBlocking {
-        val defaultConfigProviderId =
-            Identifier.create(getExtendedCameraConfigProviderId(extensionMode))
-        val cameraEventConfigProviderId =
-            Identifier.create(getCameraEventMonitorCameraConfigProviderId(extensionMode))
-
-        // Calls the ExtensionsManager#getExtensionEnabledCameraSelector() function to add the
-        // default extended camera config provider to ExtendedCameraConfigProviderStore
-        extensionsManager.getExtensionEnabledCameraSelector(baseCameraSelector, extensionMode)
-
-        // Injects the new camera config provider which will keep the original extensions needed
-        // configs and also add additional CameraEventMonitor to monitor the camera event callbacks.
-        ExtendedCameraConfigProviderStore.addConfig(cameraEventConfigProviderId) {
-                cameraInfo: CameraInfo, context: Context ->
-            // Retrieves the default extended camera config provider and
-            // ExtensionsUseCaseConfigFactory
-            val defaultCameraConfigProvider =
-                ExtendedCameraConfigProviderStore.getConfigProvider(defaultConfigProviderId)
-            val defaultCameraConfig = defaultCameraConfigProvider.getConfig(cameraInfo, context)!!
-            val defaultExtensionsUseCaseConfigFactory =
-                defaultCameraConfig.retrieveOption(CameraConfig.OPTION_USECASE_CONFIG_FACTORY, null)
-
-            // Creates a new ExtensionsCameraEventMonitorUseCaseConfigFactory on top of the default
-            // ExtensionsCameraEventMonitorUseCaseConfigFactory to monitor the capture session
-            // callbacks
-            val extensionsCameraEventMonitorUseCaseConfigFactory =
-                ExtensionsCameraEventMonitorUseCaseConfigFactory(
-                    defaultExtensionsUseCaseConfigFactory,
-                    cameraEventMonitor
-                )
-
-            // Creates the config from the original config and replaces its use case config factory
-            // with the ExtensionsCameraEventMonitorUseCaseConfigFactory
-            val mutableOptionsBundle = MutableOptionsBundle.from(defaultCameraConfig)
-            mutableOptionsBundle.insertOption(
-                CameraConfig.OPTION_USECASE_CONFIG_FACTORY,
-                extensionsCameraEventMonitorUseCaseConfigFactory
-            )
-
-            // Returns a CameraConfig implemented with the updated config
-            object : CameraConfig {
-                val config = OptionsBundle.from(mutableOptionsBundle)
-
-                override fun getConfig(): Config {
-                    return config
-                }
-
-                override fun getCompatibilityId(): Identifier {
-                    return config.retrieveOption(CameraConfig.OPTION_COMPATIBILITY_ID)!!
-                }
-            }
-        }
-    }
-
-    /**
-     * A ExtensionsCameraEventMonitorCameraFilter which includes the CameraFilter to check whether
-     * the camera is supported for the extension mode or not and also includes the identifier to
-     * find the extended camera config provider from ExtendedCameraConfigProviderStore.
-     */
-    private class ExtensionsCameraEventMonitorCameraFilter constructor(
-        private val extensionManager: ExtensionsManager,
-        @ExtensionMode.Mode private val mode: Int
-    ) : CameraFilter {
-        override fun getIdentifier(): Identifier {
-            return Identifier.create(getCameraEventMonitorCameraConfigProviderId(mode))
-        }
-
-        override fun filter(cameraInfos: MutableList<CameraInfo>): MutableList<CameraInfo> =
-            cameraInfos.mapNotNull { cameraInfo ->
-                val cameraId = Camera2CameraInfo.from(cameraInfo).cameraId
-                val cameraIdCameraSelector = CameraSelectorUtil.createCameraSelectorById(cameraId)
-                if (extensionManager.isExtensionAvailable(cameraIdCameraSelector, mode)) {
-                    cameraInfo
-                } else {
-                    null
-                }
-            }.toMutableList()
-    }
-
-    /**
-     * A UseCaseConfigFactory implemented on top of the default ExtensionsUseCaseConfigFactory to
-     * monitor the camera event callbacks
-     */
-    private class ExtensionsCameraEventMonitorUseCaseConfigFactory constructor(
-        private val useCaseConfigFactory: UseCaseConfigFactory?,
-        private val cameraEventMonitor: CameraEventMonitor
-    ) :
-        UseCaseConfigFactory {
-        override fun getConfig(
-            captureType: UseCaseConfigFactory.CaptureType,
-            captureMode: Int
-        ): Config {
-            // Retrieves the config from the default ExtensionsUseCaseConfigFactory
-            val mutableOptionsBundle = useCaseConfigFactory?.getConfig(
-                captureType, captureMode
-            )?.let {
-                MutableOptionsBundle.from(it)
-            } ?: MutableOptionsBundle.create()
-
-            // Adds the CameraEventMonitor to the original CameraEventCallbacks of ImageCapture to
-            // monitor the camera event callbacks
-            if (captureType.equals(UseCaseConfigFactory.CaptureType.IMAGE_CAPTURE)) {
-                var cameraEventCallbacks = mutableOptionsBundle.retrieveOption(
-                    Camera2ImplConfig.CAMERA_EVENT_CALLBACK_OPTION,
-                    null
-                )
-
-                if (cameraEventCallbacks != null) {
-                    cameraEventCallbacks.addAll(
-                        mutableListOf<CameraEventCallback>(
-                            cameraEventMonitor
-                        )
-                    )
-                } else {
-                    cameraEventCallbacks = CameraEventCallbacks(cameraEventMonitor)
-                }
-
-                mutableOptionsBundle.insertOption(
-                    Camera2ImplConfig.CAMERA_EVENT_CALLBACK_OPTION,
-                    cameraEventCallbacks
-                )
-            }
-
-            return OptionsBundle.from(mutableOptionsBundle)
-        }
-    }
-
-    /**
-     * An implementation of CameraEventCallback to monitor whether the camera event callbacks are
-     * called properly or not.
-     */
-    private class CameraEventMonitor : CameraEventCallback() {
+    private class CameraSessionMonitor {
         private var sessionEnabledLatch = CountDownLatch(1)
         private var sessionDisabledLatch = CountDownLatch(1)
 
-        override fun onEnableSession(): CaptureConfig? {
+        fun onOpenedSession() {
             sessionEnabledLatch.countDown()
-            return super.onEnableSession()
         }
 
-        override fun onDisableSession(): CaptureConfig? {
+        fun onClosedSession() {
             sessionDisabledLatch.countDown()
-            return super.onDisableSession()
         }
 
         fun reset() {
@@ -418,11 +278,11 @@
             sessionDisabledLatch = CountDownLatch(1)
         }
 
-        fun awaitSessionEnabledAndAssert() {
+        fun awaitSessionOpenedAndAssert() {
             assertThat(sessionEnabledLatch.await(3000, TimeUnit.MILLISECONDS)).isTrue()
         }
 
-        fun awaitSessionDisabledAndAssert() {
+        fun awaitSessionClosedAndAssert() {
             assertThat(sessionDisabledLatch.await(3000, TimeUnit.MILLISECONDS)).isTrue()
         }
     }
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/MapTemplateWithPaneDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/MapTemplateWithPaneDemoScreen.java
index 520973f..8049bf3 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/MapTemplateWithPaneDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/navigationdemos/MapTemplateWithPaneDemoScreen.java
@@ -179,8 +179,7 @@
                 // Row with a large image.
                 return new Row.Builder()
                         .setTitle(getCarContext().getString(R.string.first_row_title))
-                        .addText(getCarContext().getString(R.string.first_row_text))
-                        .addText(getCarContext().getString(R.string.first_row_text))
+                        .addText(getCarContext().getString(R.string.long_line_text))
                         .setImage(new CarIcon.Builder(mRowLargeIcon).build())
                         .build();
             default:
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ParkedVsDrivingDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ParkedVsDrivingDemoScreen.java
index ba198e9..3b10c80 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ParkedVsDrivingDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/settings/ParkedVsDrivingDemoScreen.java
@@ -90,6 +90,15 @@
                         .addText(getCarContext().getString(R.string.parked_only_text))
                         .build());
 
+        // Add a few rows with long subtext
+        for (int rowIndex = 1; rowIndex < 5; rowIndex++) {
+            listBuilder.addItem(
+                    buildRowForTemplate(
+                            R.string.other_row_title_prefix,
+                            rowIndex,
+                            R.string.long_line_text));
+        }
+
         return new ListTemplate.Builder()
                 .setSingleList(listBuilder.build())
                 .setTitle(getCarContext().getString(R.string.parking_vs_driving_demo_title))
@@ -100,4 +109,12 @@
     private void onClick(String text) {
         CarToast.makeText(getCarContext(), text, LENGTH_LONG).show();
     }
+
+    private Row buildRowForTemplate(int title, int index, int subText) {
+        String rowTitle = getCarContext().getString(title) + " " + (index + 1);
+        return new Row.Builder()
+                .setTitle(rowTitle)
+                .addText(getCarContext().getString(subText))
+                .build();
+    }
 }
diff --git a/collection/collection/build.gradle b/collection/collection/build.gradle
index a3b11b3..0ac4f14 100644
--- a/collection/collection/build.gradle
+++ b/collection/collection/build.gradle
@@ -131,7 +131,7 @@
     // collection-ktx transitively, which would lead to duplicate definition since the -ktx
     // extensions were moved into the main artifact.
     constraints {
-        jvmImplementation("androidx.collection:collection-ktx:1.3.0-alpha01")
+        jvmMainImplementation("androidx.collection:collection-ktx:1.3.0-alpha01")
     }
 }
 
diff --git a/compose/animation/animation-core/api/current.ignore b/compose/animation/animation-core/api/current.ignore
index 7507c59..ae54e41 100644
--- a/compose/animation/animation-core/api/current.ignore
+++ b/compose/animation/animation-core/api/current.ignore
@@ -3,9 +3,3 @@
     Method androidx.compose.animation.core.InfiniteTransitionKt.animateFloat has changed return type from androidx.compose.runtime.State<java.lang.Float> to androidx.compose.runtime.State<? extends java.lang.Float>
 ChangedType: androidx.compose.animation.core.InfiniteTransitionKt#animateValue(androidx.compose.animation.core.InfiniteTransition, T, T, androidx.compose.animation.core.TwoWayConverter<T,V>, androidx.compose.animation.core.InfiniteRepeatableSpec<T>):
     Method androidx.compose.animation.core.InfiniteTransitionKt.animateValue has changed return type from androidx.compose.runtime.State<T> to androidx.compose.runtime.State<? extends T>
-
-
-InvalidNullConversion: androidx.compose.animation.core.InfiniteTransitionKt#animateFloat(androidx.compose.animation.core.InfiniteTransition, float, float, androidx.compose.animation.core.InfiniteRepeatableSpec<java.lang.Float>):
-    Attempted to remove @NonNull annotation from method androidx.compose.animation.core.InfiniteTransitionKt.animateFloat(androidx.compose.animation.core.InfiniteTransition,float,float,androidx.compose.animation.core.InfiniteRepeatableSpec<java.lang.Float>)
-InvalidNullConversion: androidx.compose.animation.core.InfiniteTransitionKt#animateValue(androidx.compose.animation.core.InfiniteTransition, T, T, androidx.compose.animation.core.TwoWayConverter<T,V>, androidx.compose.animation.core.InfiniteRepeatableSpec<T>):
-    Attempted to remove @NonNull annotation from method androidx.compose.animation.core.InfiniteTransitionKt.animateValue(androidx.compose.animation.core.InfiniteTransition,T,T,androidx.compose.animation.core.TwoWayConverter<T,V>,androidx.compose.animation.core.InfiniteRepeatableSpec<T>)
diff --git a/compose/animation/animation-core/api/restricted_current.ignore b/compose/animation/animation-core/api/restricted_current.ignore
index 7507c59..ae54e41 100644
--- a/compose/animation/animation-core/api/restricted_current.ignore
+++ b/compose/animation/animation-core/api/restricted_current.ignore
@@ -3,9 +3,3 @@
     Method androidx.compose.animation.core.InfiniteTransitionKt.animateFloat has changed return type from androidx.compose.runtime.State<java.lang.Float> to androidx.compose.runtime.State<? extends java.lang.Float>
 ChangedType: androidx.compose.animation.core.InfiniteTransitionKt#animateValue(androidx.compose.animation.core.InfiniteTransition, T, T, androidx.compose.animation.core.TwoWayConverter<T,V>, androidx.compose.animation.core.InfiniteRepeatableSpec<T>):
     Method androidx.compose.animation.core.InfiniteTransitionKt.animateValue has changed return type from androidx.compose.runtime.State<T> to androidx.compose.runtime.State<? extends T>
-
-
-InvalidNullConversion: androidx.compose.animation.core.InfiniteTransitionKt#animateFloat(androidx.compose.animation.core.InfiniteTransition, float, float, androidx.compose.animation.core.InfiniteRepeatableSpec<java.lang.Float>):
-    Attempted to remove @NonNull annotation from method androidx.compose.animation.core.InfiniteTransitionKt.animateFloat(androidx.compose.animation.core.InfiniteTransition,float,float,androidx.compose.animation.core.InfiniteRepeatableSpec<java.lang.Float>)
-InvalidNullConversion: androidx.compose.animation.core.InfiniteTransitionKt#animateValue(androidx.compose.animation.core.InfiniteTransition, T, T, androidx.compose.animation.core.TwoWayConverter<T,V>, androidx.compose.animation.core.InfiniteRepeatableSpec<T>):
-    Attempted to remove @NonNull annotation from method androidx.compose.animation.core.InfiniteTransitionKt.animateValue(androidx.compose.animation.core.InfiniteTransition,T,T,androidx.compose.animation.core.TwoWayConverter<T,V>,androidx.compose.animation.core.InfiniteRepeatableSpec<T>)
diff --git a/compose/animation/animation/api/current.ignore b/compose/animation/animation/api/current.ignore
index c62b405..0b4033d 100644
--- a/compose/animation/animation/api/current.ignore
+++ b/compose/animation/animation/api/current.ignore
@@ -1,7 +1,3 @@
 // Baseline format: 1.0
 ChangedType: androidx.compose.animation.TransitionKt#animateColor(androidx.compose.animation.core.InfiniteTransition, long, long, androidx.compose.animation.core.InfiniteRepeatableSpec<androidx.compose.ui.graphics.Color>):
     Method androidx.compose.animation.TransitionKt.animateColor has changed return type from androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> to androidx.compose.runtime.State<? extends androidx.compose.ui.graphics.Color>
-
-
-InvalidNullConversion: androidx.compose.animation.TransitionKt#animateColor(androidx.compose.animation.core.InfiniteTransition, long, long, androidx.compose.animation.core.InfiniteRepeatableSpec<androidx.compose.ui.graphics.Color>):
-    Attempted to remove @NonNull annotation from method androidx.compose.animation.TransitionKt.animateColor(androidx.compose.animation.core.InfiniteTransition,long,long,androidx.compose.animation.core.InfiniteRepeatableSpec<androidx.compose.ui.graphics.Color>)
diff --git a/compose/animation/animation/api/restricted_current.ignore b/compose/animation/animation/api/restricted_current.ignore
index c62b405..0b4033d 100644
--- a/compose/animation/animation/api/restricted_current.ignore
+++ b/compose/animation/animation/api/restricted_current.ignore
@@ -1,7 +1,3 @@
 // Baseline format: 1.0
 ChangedType: androidx.compose.animation.TransitionKt#animateColor(androidx.compose.animation.core.InfiniteTransition, long, long, androidx.compose.animation.core.InfiniteRepeatableSpec<androidx.compose.ui.graphics.Color>):
     Method androidx.compose.animation.TransitionKt.animateColor has changed return type from androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> to androidx.compose.runtime.State<? extends androidx.compose.ui.graphics.Color>
-
-
-InvalidNullConversion: androidx.compose.animation.TransitionKt#animateColor(androidx.compose.animation.core.InfiniteTransition, long, long, androidx.compose.animation.core.InfiniteRepeatableSpec<androidx.compose.ui.graphics.Color>):
-    Attempted to remove @NonNull annotation from method androidx.compose.animation.TransitionKt.animateColor(androidx.compose.animation.core.InfiniteTransition,long,long,androidx.compose.animation.core.InfiniteRepeatableSpec<androidx.compose.ui.graphics.Color>)
diff --git a/compose/compiler/compiler-daemon/src/main/kotlin/androidx/compose/compiler/daemon/Compiler.kt b/compose/compiler/compiler-daemon/src/main/kotlin/androidx/compose/compiler/daemon/Compiler.kt
index 3038c88..976bfa8 100644
--- a/compose/compiler/compiler-daemon/src/main/kotlin/androidx/compose/compiler/daemon/Compiler.kt
+++ b/compose/compiler/compiler-daemon/src/main/kotlin/androidx/compose/compiler/daemon/Compiler.kt
@@ -98,7 +98,7 @@
         workingDir = Files.createTempDirectory("workingDir").toFile(),
         reporter = BuildReporter(DoNothingICReporter, DoNothingBuildMetricsReporter),
         usePreciseJavaTracking = true,
-        outputFiles = emptyList(),
+        outputDirs = null,
         buildHistoryFile = Files.createTempFile("build-history", ".bin").toFile(),
         modulesApiHistory = EmptyModulesApiHistory,
         kotlinSourceFilesExtensions = DEFAULT_KOTLIN_SOURCE_FILES_EXTENSIONS,
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
index 3c1d258..e0d3e00 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ComposerParamSignatureTests.kt
@@ -533,6 +533,7 @@
               public final useAB()V
               static <clinit>()V
               public final static I %stable
+              public final static INNERCLASS A%B A B
             }
         """
     )
@@ -931,8 +932,8 @@
               public synthetic bridge invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
               final synthetic I %x
               OUTERCLASS TestKt App (ILandroidx/compose/runtime/Composer;I)V
-              final static INNERCLASS TestKt%App%1%1 null null
               final static INNERCLASS TestKt%App%1 null null
+              final static INNERCLASS TestKt%App%1%1 null null
             }
             final class TestKt%App%1%1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function3 {
               <init>(II)V
@@ -1296,8 +1297,8 @@
               static <clinit>()V
               public final static LTestKt%J%1; INSTANCE
               OUTERCLASS TestKt J ()V
-              final static INNERCLASS TestKt%J%1%1 null null
               final static INNERCLASS TestKt%J%1 null null
+              final static INNERCLASS TestKt%J%1%1 null null
             }
             final class TestKt%J%1%1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function0 {
               <init>()V
@@ -1484,8 +1485,8 @@
               public final compute(ILandroidx/compose/runtime/Composer;I)V
               final synthetic LA; %a
               OUTERCLASS TestKt Example (LA;)V
-              final static INNERCLASS TestKt%Example%1%compute%1 null null
               final static INNERCLASS TestKt%Example%1 null null
+              final static INNERCLASS TestKt%Example%1%compute%1 null null
             }
             final class TestKt%Example%1%compute%1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function2 {
               <init>(LTestKt%Example%1;II)V
@@ -1654,6 +1655,7 @@
               public final static LTestKt%Test%1; INSTANCE
               OUTERCLASS TestKt Test (Landroidx/compose/runtime/Composer;I)V
               final static INNERCLASS TestKt%Test%1 null null
+              public final static INNERCLASS androidx/compose/ui/graphics/Color%Companion androidx/compose/ui/graphics/Color Companion
             }
             final class TestKt%Test%2 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function2 {
               <init>(I)V
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
index e368c06..bba70db 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
@@ -26,7 +26,7 @@
 import org.jetbrains.kotlin.compiler.plugin.CliOption
 import org.jetbrains.kotlin.compiler.plugin.CliOptionProcessingException
 import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
-import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
+import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
 import org.jetbrains.kotlin.config.CompilerConfiguration
 import org.jetbrains.kotlin.config.CompilerConfigurationKey
 import org.jetbrains.kotlin.config.JVMConfigurationKeys
@@ -61,6 +61,7 @@
         CompilerConfigurationKey<Boolean>("Generate decoy methods in IR transform")
 }
 
+@OptIn(ExperimentalCompilerApi::class)
 class ComposeCommandLineProcessor : CommandLineProcessor {
     companion object {
         val PLUGIN_ID = "androidx.compose.compiler.plugins.kotlin"
@@ -188,7 +189,9 @@
     }
 }
 
-class ComposeComponentRegistrar : ComponentRegistrar {
+@OptIn(ExperimentalCompilerApi::class)
+class ComposeComponentRegistrar :
+    @Suppress("DEPRECATION") org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar {
     override fun registerProjectComponents(
         project: MockProject,
         configuration: CompilerConfiguration
@@ -201,7 +204,7 @@
 
     companion object {
         fun checkCompilerVersion(configuration: CompilerConfiguration): Boolean {
-            val KOTLIN_VERSION_EXPECTATION = "1.7.21"
+            val KOTLIN_VERSION_EXPECTATION = "1.8.0"
             KotlinCompilerVersion.getVersion()?.let { version ->
                 val msgCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
                 val suppressKotlinVersionCheck = configuration.get(
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
index e2f160a..43a41cf 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
@@ -98,10 +98,12 @@
             8603 to "1.3.1",
             8604 to "1.3.2",
             8605 to "1.3.3",
+            8606 to "1.3.4",
             9000 to "1.4.0-alpha01",
             9001 to "1.4.0-alpha02",
             9100 to "1.4.0-alpha03",
             9200 to "1.4.0-alpha04",
+            9300 to "1.4.0-alpha05",
         )
 
         /**
@@ -114,7 +116,7 @@
          * The maven version string of this compiler. This string should be updated before/after every
          * release.
          */
-        const val compilerVersion: String = "1.4.0-alpha04"
+        const val compilerVersion: String = "1.4.0"
         private val minimumRuntimeVersion: String
             get() = runtimeVersionToMavenVersionTable[minimumRuntimeVersionInt] ?: "unknown"
     }
diff --git a/compose/compiler/compiler/integration-tests/src/test/kotlin/androidx/compose/compiler/test/CompilerPluginRuntimeVersionCheckTest.kt b/compose/compiler/compiler/integration-tests/src/test/kotlin/androidx/compose/compiler/test/CompilerPluginRuntimeVersionCheckTest.kt
index c3191b3..fd0bf61 100644
--- a/compose/compiler/compiler/integration-tests/src/test/kotlin/androidx/compose/compiler/test/CompilerPluginRuntimeVersionCheckTest.kt
+++ b/compose/compiler/compiler/integration-tests/src/test/kotlin/androidx/compose/compiler/test/CompilerPluginRuntimeVersionCheckTest.kt
@@ -190,6 +190,14 @@
             dependencies {
                 $dependenciesBlock
             }
+
+            tasks.withType(
+                org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+            ).configureEach {
+                kotlinOptions {
+                    jvmTarget = "1.8"
+                }
+            }
             """.trimIndent()
         )
     }
diff --git a/compose/foundation/foundation-newtext/build.gradle b/compose/foundation/foundation-newtext/build.gradle
index 1878a73..85d210a 100644
--- a/compose/foundation/foundation-newtext/build.gradle
+++ b/compose/foundation/foundation-newtext/build.gradle
@@ -42,6 +42,7 @@
         implementation(libs.kotlinStdlibCommon)
         implementation(project(':compose:foundation:foundation'))
         implementation(project(":compose:foundation:foundation-layout"))
+        implementation(project(":emoji2:emoji2"))
 
         implementation("androidx.compose.ui:ui-text:1.2.1")
         implementation("androidx.compose.ui:ui-util:1.2.1")
@@ -99,6 +100,11 @@
             }
             androidMain.dependencies {
                 api("androidx.annotation:annotation:1.1.0")
+                implementation(project(":emoji2:emoji2"))
+            }
+
+            desktopMain.dependencies {
+                implementation(libs.kotlinStdlib)
             }
 
             androidTest.dependencies {
diff --git a/compose/foundation/foundation-newtext/src/androidAndroidTest/kotlin/androidx/compose/foundation/newtext/text/CoreTextInlineContentTest.kt b/compose/foundation/foundation-newtext/src/androidAndroidTest/kotlin/androidx/compose/foundation/newtext/text/CoreTextInlineContentTest.kt
index f634151..9adc7cd 100644
--- a/compose/foundation/foundation-newtext/src/androidAndroidTest/kotlin/androidx/compose/foundation/newtext/text/CoreTextInlineContentTest.kt
+++ b/compose/foundation/foundation-newtext/src/androidAndroidTest/kotlin/androidx/compose/foundation/newtext/text/CoreTextInlineContentTest.kt
@@ -48,7 +48,6 @@
 import com.google.common.truth.Truth.assertThat
 import com.nhaarman.mockitokotlin2.mock
 import com.nhaarman.mockitokotlin2.verify
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -66,7 +65,6 @@
 
     @OptIn(ExperimentalTextApi::class)
     @Test
-    @Ignore // not implemented yet
     fun placeholder_changeSize_updateInlineContentSize() {
         // Callback to monitor the size changes of a composable.
         val onSizeChanged: (IntSize) -> Unit = mock()
@@ -113,7 +111,6 @@
     }
 
     @Test
-    @Ignore // not implemented yet
     fun rtlLayout_inlineContent_placement() {
         rule.setContent {
             CompositionLocalProvider(
@@ -133,7 +130,6 @@
     }
 
     @Test
-    @Ignore // not implemented yet
     fun rtlTextContent_inlineContent_placement() {
         rule.setContent {
             // RTL character, supported by sample_font
@@ -149,7 +145,6 @@
     }
 
     @Test
-    @Ignore // not implemented yet
     fun rtlTextDirection_inlineContent_placement() {
         rule.setContent {
             // LTR character, supported by sample_font
@@ -166,7 +161,6 @@
     }
 
     @Test
-    @Ignore // not implemented yet
     fun bidiText_inlineContent_placement() {
         rule.setContent {
             // RTL and LTR characters, supported by sample_font
@@ -182,7 +176,6 @@
     }
 
     @Test
-    @Ignore // not implemented yet
     fun bidiText_2_inlineContent_placement() {
         rule.setContent {
             // RTL and LTR characters, supported by sample_font
diff --git a/compose/foundation/foundation-newtext/src/androidAndroidTest/kotlin/androidx/compose/foundation/newtext/text/modifiers/MultiParagraphLayoutCacheTest.kt b/compose/foundation/foundation-newtext/src/androidAndroidTest/kotlin/androidx/compose/foundation/newtext/text/modifiers/MultiParagraphLayoutCacheTest.kt
index d5780c1..471b37e 100644
--- a/compose/foundation/foundation-newtext/src/androidAndroidTest/kotlin/androidx/compose/foundation/newtext/text/modifiers/MultiParagraphLayoutCacheTest.kt
+++ b/compose/foundation/foundation-newtext/src/androidAndroidTest/kotlin/androidx/compose/foundation/newtext/text/modifiers/MultiParagraphLayoutCacheTest.kt
@@ -52,13 +52,12 @@
             val spanStyle = SpanStyle(fontSize = fontSize, fontFamily = fontFamily)
             val annotatedString = AnnotatedString(text, spanStyle)
             val textDelegate = MultiParagraphLayoutCache(
-                TextInlineContentLayoutDrawParams(
-                    text = annotatedString,
-                    style = TextStyle.Default,
-                    fontFamilyResolver = fontFamilyResolver,
-                ),
-                density = this
-            )
+                text = annotatedString,
+                style = TextStyle.Default,
+                fontFamilyResolver = fontFamilyResolver,
+            ).also {
+                it.density = this
+            }
 
             textDelegate.layoutWithConstraints(Constraints.fixed(0, 0), LayoutDirection.Ltr)
 
@@ -75,13 +74,12 @@
             val spanStyle = SpanStyle(fontSize = fontSize, fontFamily = fontFamily)
             val annotatedString = AnnotatedString(text, spanStyle)
             val textDelegate = MultiParagraphLayoutCache(
-                TextInlineContentLayoutDrawParams(
-                    text = annotatedString,
-                    style = TextStyle.Default,
-                    fontFamilyResolver = fontFamilyResolver,
-                ),
-                density = this
-            )
+                text = annotatedString,
+                style = TextStyle.Default,
+                fontFamilyResolver = fontFamilyResolver,
+            ).also {
+                it.density = this
+            }
 
             textDelegate.layoutWithConstraints(Constraints.fixed(0, 0), LayoutDirection.Ltr)
 
@@ -93,13 +91,12 @@
     @Test
     fun TextLayoutInput_reLayout_withDifferentHeight() {
         val textDelegate = MultiParagraphLayoutCache(
-            TextInlineContentLayoutDrawParams(
-                text = AnnotatedString(text = "Hello World!"),
-                style = TextStyle.Default,
-                fontFamilyResolver = fontFamilyResolver,
-            ),
-            density = density
-        )
+            text = AnnotatedString("Hello World"),
+            style = TextStyle.Default,
+            fontFamilyResolver = fontFamilyResolver,
+        ).also {
+            it.density = density
+        }
         val width = 200
         val heightFirstLayout = 100
         val heightSecondLayout = 200
@@ -121,13 +118,12 @@
     @Test
     fun TextLayoutResult_reLayout_withDifferentHeight() {
         val textDelegate = MultiParagraphLayoutCache(
-            TextInlineContentLayoutDrawParams(
-                text = AnnotatedString(text = "Hello World!"),
-                style = TextStyle.Default,
-                fontFamilyResolver = fontFamilyResolver,
-            ),
-            density = density
-        )
+            text = AnnotatedString("Hello World"),
+            style = TextStyle.Default,
+            fontFamilyResolver = fontFamilyResolver,
+        ).also {
+            it.density = density
+        }
         val width = 200
         val heightFirstLayout = 100
         val heightSecondLayout = 200
@@ -151,16 +147,14 @@
         val fontSize = 20f
         val text = AnnotatedString(text = "Hello World! Hello World! Hello World! Hello World!")
         val textDelegate = MultiParagraphLayoutCache(
-            TextInlineContentLayoutDrawParams(
-                text = text,
-                style = TextStyle(fontSize = fontSize.sp),
-                fontFamilyResolver = fontFamilyResolver,
-                softWrap = false,
-                overflow = TextOverflow.Ellipsis,
-            ),
-
-            density = density
-        )
+            text = text,
+            style = TextStyle(fontSize = fontSize.sp),
+            fontFamilyResolver = fontFamilyResolver,
+            softWrap = false,
+            overflow = TextOverflow.Ellipsis,
+        ).also {
+            it.density = density
+        }
 
         textDelegate.layoutWithConstraints(Constraints.fixed(0, 0), LayoutDirection.Ltr)
         // Makes width smaller than needed.
@@ -177,15 +171,15 @@
     fun TextLayoutResult_layoutWithLimitedHeight_withEllipsis() {
         val fontSize = 20f
         val text = AnnotatedString(text = "Hello World! Hello World! Hello World! Hello World!")
+
         val textDelegate = MultiParagraphLayoutCache(
-            TextInlineContentLayoutDrawParams(
-                text = text,
-                style = TextStyle(fontSize = fontSize.sp),
-                fontFamilyResolver = fontFamilyResolver,
-                overflow = TextOverflow.Ellipsis,
-            ),
-            density = density
-        )
+            text = text,
+            style = TextStyle(fontSize = fontSize.sp),
+            fontFamilyResolver = fontFamilyResolver,
+            overflow = TextOverflow.Ellipsis,
+        ).also {
+            it.density = density
+        }
         textDelegate.layoutWithConstraints(Constraints.fixed(0, 0), LayoutDirection.Ltr)
         val constraints = Constraints(
             maxWidth = textDelegate.maxIntrinsicWidth / 4,
@@ -202,15 +196,16 @@
     fun TextLayoutResult_sameWidth_inRtlAndLtr_withLetterSpacing() {
         val fontSize = 20f
         val text = AnnotatedString(text = "Hello World")
+
         val textDelegate = MultiParagraphLayoutCache(
-            TextInlineContentLayoutDrawParams(
-                text = text,
-                style = TextStyle(fontSize = fontSize.sp, letterSpacing = 0.5.sp),
-                fontFamilyResolver = fontFamilyResolver,
-                overflow = TextOverflow.Ellipsis,
-            ),
-            density = density
-        )
+            text = text,
+            style = TextStyle(fontSize = fontSize.sp, letterSpacing = 0.5.sp),
+            fontFamilyResolver = fontFamilyResolver,
+            overflow = TextOverflow.Ellipsis,
+        ).also {
+            it.density = density
+        }
+
         textDelegate.layoutWithConstraints(Constraints(), LayoutDirection.Ltr)
         val layoutResultLtr = textDelegate.layout
         textDelegate.layoutWithConstraints(Constraints(), LayoutDirection.Rtl)
diff --git a/compose/foundation/foundation-newtext/src/androidAndroidTest/kotlin/androidx/compose/foundation/newtext/text/modifiers/MultiParagraphLayoutCacheWidthWithLetterSpacingTest.kt b/compose/foundation/foundation-newtext/src/androidAndroidTest/kotlin/androidx/compose/foundation/newtext/text/modifiers/MultiParagraphLayoutCacheWidthWithLetterSpacingTest.kt
index e0edf29..a2195df 100644
--- a/compose/foundation/foundation-newtext/src/androidAndroidTest/kotlin/androidx/compose/foundation/newtext/text/modifiers/MultiParagraphLayoutCacheWidthWithLetterSpacingTest.kt
+++ b/compose/foundation/foundation-newtext/src/androidAndroidTest/kotlin/androidx/compose/foundation/newtext/text/modifiers/MultiParagraphLayoutCacheWidthWithLetterSpacingTest.kt
@@ -73,18 +73,17 @@
 
     private fun assertLineCount(style: TextStyle) {
         val textDelegate = MultiParagraphLayoutCache(
-            TextInlineContentLayoutDrawParams(
-                text = AnnotatedString(text = "This is a callout message"),
-                style = style.copy(
-                    fontFamily = fontFamily,
-                    fontSize = fontSize
-                ),
-                fontFamilyResolver = fontFamilyResolver,
-                softWrap = true,
-                overflow = TextOverflow.Clip
+            text = AnnotatedString(text = "This is a callout message"),
+            style = style.copy(
+                fontFamily = fontFamily,
+                fontSize = fontSize
             ),
-            density = density,
-        )
+            fontFamilyResolver = fontFamilyResolver,
+            softWrap = true,
+            overflow = TextOverflow.Clip
+        ).also {
+            it.density = density
+        }
         textDelegate.layoutWithConstraints(Constraints(), LayoutDirection.Ltr)
         val layoutResult = textDelegate.layout
         assertThat(layoutResult.lineCount).isEqualTo(1)
diff --git a/compose/foundation/foundation-newtext/src/androidAndroidTest/kotlin/androidx/compose/foundation/newtext/text/modifiers/TextLayoutResultIntegrationTest.kt b/compose/foundation/foundation-newtext/src/androidAndroidTest/kotlin/androidx/compose/foundation/newtext/text/modifiers/TextLayoutResultIntegrationTest.kt
index 3adb8f6d..4b8b7c1 100644
--- a/compose/foundation/foundation-newtext/src/androidAndroidTest/kotlin/androidx/compose/foundation/newtext/text/modifiers/TextLayoutResultIntegrationTest.kt
+++ b/compose/foundation/foundation-newtext/src/androidAndroidTest/kotlin/androidx/compose/foundation/newtext/text/modifiers/TextLayoutResultIntegrationTest.kt
@@ -56,13 +56,12 @@
             val spanStyle = SpanStyle(fontSize = fontSize, fontFamily = fontFamily)
             val annotatedString = AnnotatedString(text, spanStyle)
             val textDelegate = MultiParagraphLayoutCache(
-                TextInlineContentLayoutDrawParams(
-                    text = annotatedString,
-                    style = TextStyle.Default,
-                    fontFamilyResolver = fontFamilyResolver
-                ),
-                density = this,
-            )
+                text = annotatedString,
+                style = TextStyle.Default,
+                fontFamilyResolver = fontFamilyResolver
+            ).also {
+                it.density = this
+            }
 
             textDelegate.layoutWithConstraints(Constraints(0, 200), layoutDirection)
             val layoutResult = textDelegate.layout
@@ -80,13 +79,12 @@
         val spanStyle = SpanStyle(fontSize = 20.sp, fontFamily = fontFamily)
         val annotatedString = AnnotatedString(text, spanStyle)
         val textDelegate = MultiParagraphLayoutCache(
-            TextInlineContentLayoutDrawParams(
-                text = annotatedString,
-                style = TextStyle.Default,
-                fontFamilyResolver = fontFamilyResolver
-            ),
-            density = density,
-        )
+            text = annotatedString,
+            style = TextStyle.Default,
+            fontFamilyResolver = fontFamilyResolver
+        ).also {
+            it.density = density
+        }
 
         textDelegate.layoutWithConstraints(Constraints(maxWidth = width), layoutDirection)
         val layoutResult = textDelegate.layout
@@ -102,13 +100,12 @@
             val text = "hello"
             val annotatedString = AnnotatedString(text, spanStyle)
             val textDelegate = MultiParagraphLayoutCache(
-                TextInlineContentLayoutDrawParams(
-                    text = annotatedString,
-                    style = TextStyle.Default,
-                    fontFamilyResolver = fontFamilyResolver
-                ),
-                density = this,
-            )
+                text = annotatedString,
+                style = TextStyle.Default,
+                fontFamilyResolver = fontFamilyResolver
+            ).also {
+                it.density = this
+            }
 
             textDelegate.layoutWithConstraints(Constraints(), layoutDirection)
             val layoutResult = textDelegate.layout
@@ -120,13 +117,12 @@
     @Test
     fun layout_build_layoutResult() {
         val textDelegate = MultiParagraphLayoutCache(
-            TextInlineContentLayoutDrawParams(
-                text = AnnotatedString("Hello"),
-                style = TextStyle.Default,
-                fontFamilyResolver = fontFamilyResolver
-            ),
-            density = density,
-        )
+            text = AnnotatedString("hello"),
+            style = TextStyle.Default,
+            fontFamilyResolver = fontFamilyResolver
+        ).also {
+            it.density = density
+        }
 
         textDelegate.layoutWithConstraints(Constraints(0, 20), layoutDirection)
         val layoutResult = textDelegate.layout
@@ -147,13 +143,12 @@
         )
 
         val textDelegate = MultiParagraphLayoutCache(
-            TextInlineContentLayoutDrawParams(
-                text = annotatedString,
-                style = TextStyle.Default,
-                fontFamilyResolver = fontFamilyResolver
-            ),
-            density = density,
-        )
+            text = annotatedString,
+            style = TextStyle.Default,
+            fontFamilyResolver = fontFamilyResolver
+        ).also {
+            it.density = density
+        }
         textDelegate.layoutWithConstraints(Constraints(), layoutDirection)
         val layoutResult = textDelegate.layout
 
@@ -175,13 +170,12 @@
             )
 
             val textDelegate = MultiParagraphLayoutCache(
-                TextInlineContentLayoutDrawParams(
-                    text = annotatedString,
-                    style = TextStyle.Default,
-                    fontFamilyResolver = fontFamilyResolver
-                ),
-                density = this,
-            )
+                text = annotatedString,
+                style = TextStyle.Default,
+                fontFamilyResolver = fontFamilyResolver
+            ).also {
+                it.density = this
+            }
             textDelegate.layoutWithConstraints(Constraints(), layoutDirection)
             val layoutResult = textDelegate.layout
 
@@ -199,13 +193,12 @@
         val spanStyle = SpanStyle(fontSize = 20.sp, fontFamily = fontFamily)
         val annotatedString = AnnotatedString(text, spanStyle)
         val textDelegate = MultiParagraphLayoutCache(
-            TextInlineContentLayoutDrawParams(
-                text = annotatedString,
-                style = TextStyle.Default,
-                fontFamilyResolver = fontFamilyResolver
-            ),
-            density = density,
-        )
+            text = annotatedString,
+            style = TextStyle.Default,
+            fontFamilyResolver = fontFamilyResolver
+        ).also {
+            it.density = density
+        }
 
         textDelegate.layoutWithConstraints(Constraints(), layoutDirection)
         val layoutResult = textDelegate.layout
@@ -224,14 +217,13 @@
         val maxLines = 3
 
         val textDelegate = MultiParagraphLayoutCache(
-            TextInlineContentLayoutDrawParams(
-                text = annotatedString,
-                style = TextStyle.Default,
-                fontFamilyResolver = fontFamilyResolver,
-                maxLines = maxLines
-            ),
-            density = density,
-        )
+            text = annotatedString,
+            style = TextStyle.Default,
+            fontFamilyResolver = fontFamilyResolver,
+            maxLines = maxLines
+        ).also {
+            it.density = density
+        }
 
         textDelegate.layoutWithConstraints(Constraints.fixed(0, 0), LayoutDirection.Ltr)
         // Tries to make 5 lines of text, which exceeds the given maxLines(3).
@@ -253,14 +245,13 @@
         val maxLines = 10
 
         val textDelegate = MultiParagraphLayoutCache(
-            TextInlineContentLayoutDrawParams(
-                text = annotatedString,
-                style = TextStyle.Default,
-                fontFamilyResolver = fontFamilyResolver,
-                maxLines = maxLines
-            ),
-            density = density,
-        )
+            text = annotatedString,
+            style = TextStyle.Default,
+            fontFamilyResolver = fontFamilyResolver,
+            maxLines = maxLines
+        ).also {
+            it.density = density
+        }
 
         textDelegate.layoutWithConstraints(Constraints.fixed(0, 0), LayoutDirection.Ltr)
         // Tries to make 5 lines of text, which doesn't exceed the given maxLines(10).
@@ -281,13 +272,12 @@
         val annotatedString = AnnotatedString(text, spanStyle)
 
         val textDelegate = MultiParagraphLayoutCache(
-            TextInlineContentLayoutDrawParams(
-                text = annotatedString,
-                style = TextStyle.Default,
-                fontFamilyResolver = fontFamilyResolver,
-            ),
-            density = density,
-        )
+            text = annotatedString,
+            style = TextStyle.Default,
+            fontFamilyResolver = fontFamilyResolver,
+        ).also {
+            it.density = density
+        }
 
         textDelegate.layoutWithConstraints(
             Constraints(),
@@ -314,13 +304,12 @@
         val annotatedString = AnnotatedString(text, spanStyle)
 
         val textDelegate = MultiParagraphLayoutCache(
-            TextInlineContentLayoutDrawParams(
-                text = annotatedString,
-                style = TextStyle.Default,
-                fontFamilyResolver = fontFamilyResolver
-            ),
-            density = density,
-        )
+            text = annotatedString,
+            style = TextStyle.Default,
+            fontFamilyResolver = fontFamilyResolver,
+        ).also {
+            it.density = density
+        }
 
         textDelegate.layoutWithConstraints(
             Constraints(),
diff --git a/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/ContextMenu.android.kt b/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/ContextMenu.android.kt
new file mode 100644
index 0000000..f907321
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/ContextMenu.android.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.newtext.text.copypasta
+
+import androidx.compose.foundation.newtext.text.copypasta.selection.SelectionManager
+import androidx.compose.runtime.Composable
+
+@Composable
+internal actual fun ContextMenuArea(
+    manager: SelectionManager,
+    content: @Composable () -> Unit
+) {
+    content()
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/StringHelpers.android.kt b/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/StringHelpers.android.kt
new file mode 100644
index 0000000..92cb068
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/StringHelpers.android.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.newtext.text.copypasta
+
+import androidx.emoji2.text.EmojiCompat
+import java.text.BreakIterator
+
+internal actual fun String.findPrecedingBreak(index: Int): Int {
+    val emojiBreak =
+        getEmojiCompatIfLoaded()?.getEmojiStart(this, maxOf(0, index - 1))?.takeUnless { it == -1 }
+    if (emojiBreak != null) return emojiBreak
+
+    val it = BreakIterator.getCharacterInstance()
+    it.setText(this)
+    return it.preceding(index)
+}
+
+internal actual fun String.findFollowingBreak(index: Int): Int {
+    val emojiBreak = getEmojiCompatIfLoaded()?.getEmojiEnd(this, index)?.takeUnless { it == -1 }
+    if (emojiBreak != null) return emojiBreak
+
+    val it = BreakIterator.getCharacterInstance()
+    it.setText(this)
+    return it.following(index)
+}
+
+private fun getEmojiCompatIfLoaded(): EmojiCompat? =
+    if (EmojiCompat.isConfigured())
+        EmojiCompat.get().takeIf { it.loadState == EmojiCompat.LOAD_STATE_SUCCEEDED }
+    else null
\ No newline at end of file
diff --git a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt b/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/TextPointerIcon.android.kt
similarity index 67%
copy from tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
copy to compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/TextPointerIcon.android.kt
index ebac64e..6645638 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
+++ b/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/TextPointerIcon.android.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-package androidx.tv.material
+package androidx.compose.foundation.newtext.text.copypasta
 
-@RequiresOptIn(
-    "This tv-material API is experimental and likely to change or be removed in the future."
-)
-@Retention(AnnotationRetention.BINARY)
-annotation class ExperimentalTvMaterialApi
\ No newline at end of file
+import androidx.compose.ui.input.pointer.PointerIcon
+
+internal actual val textPointerIcon: PointerIcon =
+    PointerIcon(android.view.PointerIcon.TYPE_TEXT)
diff --git a/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/AndroidSelectionHandles.android.kt b/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/AndroidSelectionHandles.android.kt
new file mode 100644
index 0000000..ab50488
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/AndroidSelectionHandles.android.kt
@@ -0,0 +1,327 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.newtext.text.copypasta.selection
+
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.newtext.text.copypasta.selection.HandleReferencePoint.TopLeft
+import androidx.compose.foundation.newtext.text.copypasta.selection.HandleReferencePoint.TopMiddle
+import androidx.compose.foundation.newtext.text.copypasta.selection.HandleReferencePoint.TopRight
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.draw.CacheDrawScope
+import androidx.compose.ui.draw.drawWithCache
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.BlendMode
+import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.ImageBitmapConfig
+import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
+import androidx.compose.ui.graphics.drawscope.scale
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.style.ResolvedTextDirection
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.window.Popup
+import androidx.compose.ui.window.PopupPositionProvider
+import androidx.compose.ui.window.PopupProperties
+import kotlin.math.ceil
+import kotlin.math.roundToInt
+
+@Composable
+internal actual fun SelectionHandle(
+    position: Offset,
+    isStartHandle: Boolean,
+    direction: ResolvedTextDirection,
+    handlesCrossed: Boolean,
+    modifier: Modifier,
+    content: @Composable (() -> Unit)?
+) {
+    val isLeft = isLeft(isStartHandle, direction, handlesCrossed)
+    // The left selection handle's top right is placed at the given position, and vice versa.
+    val handleReferencePoint = if (isLeft) {
+        TopRight
+    } else {
+        TopLeft
+    }
+
+    HandlePopup(position = position, handleReferencePoint = handleReferencePoint) {
+        if (content == null) {
+            DefaultSelectionHandle(
+                modifier = modifier
+                    .semantics {
+                        this[SelectionHandleInfoKey] = SelectionHandleInfo(
+                            position = position
+                        )
+                    },
+                isStartHandle = isStartHandle,
+                direction = direction,
+                handlesCrossed = handlesCrossed
+            )
+        } else {
+            content()
+        }
+    }
+}
+
+@Composable
+/*@VisibleForTesting*/
+internal fun DefaultSelectionHandle(
+    modifier: Modifier,
+    isStartHandle: Boolean,
+    direction: ResolvedTextDirection,
+    handlesCrossed: Boolean
+) {
+    Spacer(
+        modifier.size(
+            HandleWidth,
+            HandleHeight
+        )
+            .drawSelectionHandle(isStartHandle, direction, handlesCrossed)
+    )
+}
+
+@Suppress("ModifierInspectorInfo")
+internal fun Modifier.drawSelectionHandle(
+    isStartHandle: Boolean,
+    direction: ResolvedTextDirection,
+    handlesCrossed: Boolean
+) = composed {
+    val handleColor = LocalTextSelectionColors.current.handleColor
+    this.then(
+        Modifier.drawWithCache {
+            val radius = size.width / 2f
+            val handleImage = createHandleImage(radius)
+            val colorFilter = ColorFilter.tint(handleColor)
+            onDrawWithContent {
+                drawContent()
+                val isLeft = isLeft(isStartHandle, direction, handlesCrossed)
+                if (isLeft) {
+                    // Flip the selection handle horizontally.
+                    scale(scaleX = -1f, scaleY = 1f) {
+                        drawImage(
+                            image = handleImage,
+                            colorFilter = colorFilter
+                        )
+                    }
+                } else {
+                    drawImage(
+                        image = handleImage,
+                        colorFilter = colorFilter
+                    )
+                }
+            }
+        }
+    )
+}
+
+/**
+ * The cache for the image mask created to draw selection/cursor handle, so that we don't need to
+ * recreate them.
+ */
+private object HandleImageCache {
+    var imageBitmap: ImageBitmap? = null
+    var canvas: Canvas? = null
+    var canvasDrawScope: CanvasDrawScope? = null
+}
+
+/**
+ * Create an image bitmap for the basic shape of a selection handle or cursor handle. It is an
+ * circle with a rectangle covering its left top part.
+ *
+ * To draw the right selection handle, directly draw this image bitmap.
+ * To draw the left selection handle, mirror the canvas first and then draw this image bitmap.
+ * To draw the cursor handle, translate and rotated the canvas 45 degrees, then draw this image
+ * bitmap.
+ *
+ * @param radius the radius of circle in selection/cursor handle.
+ * CanvasDrawScope objects so that we only recreate them when necessary.
+ */
+internal fun CacheDrawScope.createHandleImage(radius: Float): ImageBitmap {
+    // The edge length of the square bounding box of the selection/cursor handle. This is also
+    // the size of the bitmap needed for the bitmap mask.
+    val edge = ceil(radius).toInt() * 2
+
+    var imageBitmap = HandleImageCache.imageBitmap
+    var canvas = HandleImageCache.canvas
+    var drawScope = HandleImageCache.canvasDrawScope
+
+    // If the cached bitmap is null or too small, we need to create new bitmap.
+    if (
+        imageBitmap == null ||
+        canvas == null ||
+        edge > imageBitmap.width ||
+        edge > imageBitmap.height
+    ) {
+        imageBitmap = ImageBitmap(
+            width = edge,
+            height = edge,
+            config = ImageBitmapConfig.Alpha8
+        )
+        HandleImageCache.imageBitmap = imageBitmap
+        canvas = Canvas(imageBitmap)
+        HandleImageCache.canvas = canvas
+    }
+    if (drawScope == null) {
+        drawScope = CanvasDrawScope()
+        HandleImageCache.canvasDrawScope = drawScope
+    }
+
+    drawScope.draw(
+        this,
+        layoutDirection,
+        canvas,
+        Size(imageBitmap.width.toFloat(), imageBitmap.height.toFloat())
+    ) {
+        // Clear the previously rendered portion within this ImageBitmap as we could
+        // be re-using it
+        drawRect(
+            color = Color.Black,
+            size = size,
+            blendMode = BlendMode.Clear
+        )
+
+        // Draw the rectangle at top left.
+        drawRect(
+            color = Color(0xFF000000),
+            topLeft = Offset.Zero,
+            size = Size(radius, radius)
+        )
+        // Draw the circle
+        drawCircle(
+            color = Color(0xFF000000),
+            radius = radius,
+            center = Offset(radius, radius)
+        )
+    }
+    return imageBitmap
+}
+
+@Composable
+internal fun HandlePopup(
+    position: Offset,
+    handleReferencePoint: HandleReferencePoint,
+    content: @Composable () -> Unit
+) {
+    val intOffset = IntOffset(position.x.roundToInt(), position.y.roundToInt())
+
+    val popupPositioner = remember(handleReferencePoint, intOffset) {
+        HandlePositionProvider(handleReferencePoint, intOffset)
+    }
+
+    Popup(
+        popupPositionProvider = popupPositioner,
+        properties = PopupProperties(
+            excludeFromSystemGesture = true,
+            clippingEnabled = false
+        ),
+        content = content
+    )
+}
+
+/**
+ * The enum that specifies how a selection/cursor handle is placed to its given position.
+ * When this value is [TopLeft], the top left corner of the handle will be placed at the
+ * given position.
+ * When this value is [TopRight], the top right corner of the handle will be placed at the
+ * given position.
+ * When this value is [TopMiddle], the handle top edge's middle point will be placed at the given
+ * position.
+ */
+internal enum class HandleReferencePoint {
+    TopLeft,
+    TopRight,
+    TopMiddle
+}
+
+/**
+ * This [PopupPositionProvider] for [HandlePopup]. It will position the selection handle
+ * to the [offset] in its anchor layout.
+ *
+ * @see HandleReferencePoint
+ */
+/*@VisibleForTesting*/
+internal class HandlePositionProvider(
+    private val handleReferencePoint: HandleReferencePoint,
+    private val offset: IntOffset
+) : PopupPositionProvider {
+    override fun calculatePosition(
+        anchorBounds: IntRect,
+        windowSize: IntSize,
+        layoutDirection: LayoutDirection,
+        popupContentSize: IntSize
+    ): IntOffset {
+        return when (handleReferencePoint) {
+            TopLeft ->
+                IntOffset(
+                    x = anchorBounds.left + offset.x,
+                    y = anchorBounds.top + offset.y
+                )
+            TopRight ->
+                IntOffset(
+                    x = anchorBounds.left + offset.x - popupContentSize.width,
+                    y = anchorBounds.top + offset.y
+                )
+            TopMiddle ->
+                IntOffset(
+                    x = anchorBounds.left + offset.x - popupContentSize.width / 2,
+                    y = anchorBounds.top + offset.y
+                )
+        }
+    }
+}
+
+/**
+ * Computes whether the handle's appearance should be left-pointing or right-pointing.
+ */
+private fun isLeft(
+    isStartHandle: Boolean,
+    direction: ResolvedTextDirection,
+    handlesCrossed: Boolean
+): Boolean {
+    return if (isStartHandle) {
+        isHandleLtrDirection(direction, handlesCrossed)
+    } else {
+        !isHandleLtrDirection(direction, handlesCrossed)
+    }
+}
+
+/**
+ * This method is to check if the selection handles should use the natural Ltr pointing
+ * direction.
+ * If the context is Ltr and the handles are not crossed, or if the context is Rtl and the handles
+ * are crossed, return true.
+ *
+ * In Ltr context, the start handle should point to the left, and the end handle should point to
+ * the right. However, in Rtl context or when handles are crossed, the start handle should point to
+ * the right, and the end handle should point to left.
+ */
+/*@VisibleForTesting*/
+internal fun isHandleLtrDirection(
+    direction: ResolvedTextDirection,
+    areHandlesCrossed: Boolean
+): Boolean {
+    return direction == ResolvedTextDirection.Ltr && !areHandlesCrossed ||
+        direction == ResolvedTextDirection.Rtl && areHandlesCrossed
+}
diff --git a/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionManager.android.kt b/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionManager.android.kt
new file mode 100644
index 0000000..7a7e170
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/androidMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionManager.android.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.newtext.text.copypasta.selection
+
+import android.annotation.SuppressLint
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.MagnifierStyle
+import androidx.compose.foundation.magnifier
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.IntSize
+
+// TODO(b/139322105) Implement for Android when hardware keyboard is implemented
+internal actual fun isCopyKeyEvent(keyEvent: KeyEvent) = false
+
+// We use composed{} to read a local, but don't provide inspector info because the underlying
+// magnifier modifier provides more meaningful inspector info.
+@SuppressLint("ModifierInspectorInfo")
+@OptIn(ExperimentalFoundationApi::class)
+internal actual fun Modifier.selectionMagnifier(manager: SelectionManager): Modifier {
+    // Avoid tracking animation state on older Android versions that don't support magnifiers.
+    if (!MagnifierStyle.TextDefault.isSupported) {
+        return this
+    }
+
+    return composed {
+        val density = LocalDensity.current
+        var magnifierSize by remember { mutableStateOf(IntSize.Zero) }
+        animatedSelectionMagnifier(
+            magnifierCenter = {
+                calculateSelectionMagnifierCenterAndroid(
+                    manager,
+                    magnifierSize
+                )
+            },
+            platformMagnifier = { center ->
+                Modifier
+                    .magnifier(
+                        sourceCenter = { center() },
+                        onSizeChanged = { size ->
+                            magnifierSize = with(density) {
+                                IntSize(size.width.roundToPx(), size.height.roundToPx())
+                            }
+                        },
+                        style = MagnifierStyle.TextDefault
+                    )
+            }
+        )
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/AnnotatedStringResolveInlineContent.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/AnnotatedStringResolveInlineContent.kt
index 39cfec4..26ebe38 100644
--- a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/AnnotatedStringResolveInlineContent.kt
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/AnnotatedStringResolveInlineContent.kt
@@ -62,6 +62,9 @@
     return Pair(placeholders, inlineComposables)
 }
 
+internal fun AnnotatedString.hasInlineContent(): Boolean =
+    hasStringAnnotations(INLINE_CONTENT_TAG, 0, text.length)
+
 @Composable
 internal fun InlineChildren(
     text: AnnotatedString,
@@ -81,7 +84,7 @@
 
 private typealias PlaceholderRange = AnnotatedString.Range<Placeholder>
 private typealias InlineContentRange = AnnotatedString.Range<@Composable (String) -> Unit>
-internal const val INLINE_CONTENT_TAG = "androidx.compose.foundation.newtext.text.inlineContent"
+internal const val INLINE_CONTENT_TAG = "androidx.compose.foundation.text.inlineContent"
 
 private val EmptyInlineContent: Pair<List<PlaceholderRange>, List<InlineContentRange>> =
     Pair(emptyList(), emptyList())
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/TextUsingModifier.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/TextUsingModifier.kt
index 850a7e0..819e0e1 100644
--- a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/TextUsingModifier.kt
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/TextUsingModifier.kt
@@ -16,8 +16,12 @@
 
 package androidx.compose.foundation.newtext.text
 
-import androidx.compose.foundation.newtext.text.modifiers.StaticTextModifier
-import androidx.compose.foundation.newtext.text.modifiers.TextInlineContentLayoutDrawParams
+import androidx.compose.foundation.newtext.text.copypasta.selection.LocalSelectionRegistrar
+import androidx.compose.foundation.newtext.text.copypasta.selection.LocalTextSelectionColors
+import androidx.compose.foundation.newtext.text.modifiers.SelectableTextAnnotatedStringElement
+import androidx.compose.foundation.newtext.text.modifiers.TextAnnotatedStringElement
+import androidx.compose.foundation.newtext.text.modifiers.SelectionController
+import androidx.compose.foundation.newtext.text.modifiers.validateMinMaxLines
 import androidx.compose.foundation.text.InlineTextContent
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
@@ -30,9 +34,8 @@
 import androidx.compose.ui.layout.MeasurePolicy
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.MeasureScope
-import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.platform.LocalFontFamilyResolver
-import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.ExperimentalTextApi
 import androidx.compose.ui.text.Placeholder
@@ -61,8 +64,20 @@
     maxLines: Int = Int.MAX_VALUE,
     minLines: Int = 1,
 ) {
+    validateMinMaxLines(minLines, maxLines)
+    val selectionRegistrar = LocalSelectionRegistrar.current
+    val selectionController = if (selectionRegistrar != null) {
+        val backgroundSelectionColor = LocalTextSelectionColors.current.backgroundColor
+        remember(selectionRegistrar, backgroundSelectionColor) {
+            SelectionController(
+                selectionRegistrar,
+                backgroundSelectionColor
+            )
+        }
+    } else {
+        null
+    }
     Layout(
-        content = {},
         modifier = modifier.textModifier(
             AnnotatedString(text),
             style = style,
@@ -74,8 +89,9 @@
             fontFamilyResolver = LocalFontFamilyResolver.current,
             placeholders = null,
             onPlaceholderLayout = null,
+            selectionController = selectionController
         ),
-        TextMeasurePolicy { null }
+        EmptyMeasurePolicy
     )
 }
 
@@ -95,32 +111,72 @@
     minLines: Int = 1,
     inlineContent: Map<String, InlineTextContent>? = null,
 ) {
-    val fontFamilyResolver = LocalFontFamilyResolver.current
-
-    val (placeholders, inlineComposables) = text.resolveInlineContent(inlineContent)
-    val measuredPlaceholderPositions = remember {
-        mutableStateOf<List<Rect?>?>(null)
+    validateMinMaxLines(minLines, maxLines)
+    val selectionRegistrar = LocalSelectionRegistrar.current
+    val selectionController = if (selectionRegistrar != null) {
+        val backgroundSelectionColor = LocalTextSelectionColors.current.backgroundColor
+        remember(selectionRegistrar, backgroundSelectionColor) {
+            SelectionController(
+                selectionRegistrar,
+                backgroundSelectionColor
+            )
+        }
+    } else {
+        null
     }
-    Layout(
-        content = if (inlineComposables.isEmpty()) {
-            {}
-        } else {
-            { InlineChildren(text, inlineComposables) }
-        },
-        modifier = modifier.textModifier(
-            text = text,
-            style = style,
-            onTextLayout = onTextLayout,
-            overflow = overflow,
-            softWrap = softWrap,
-            maxLines = maxLines,
-            minLines = minLines,
-            fontFamilyResolver = fontFamilyResolver,
-            placeholders = placeholders,
-            onPlaceholderLayout = { measuredPlaceholderPositions.value = it }
-        ),
-        measurePolicy = TextMeasurePolicy { measuredPlaceholderPositions.value }
-    )
+
+    if (!text.hasInlineContent()) {
+        // this is the same as text: String, use all the early exits
+        Layout(
+            modifier = modifier.textModifier(
+                text = text,
+                style = style,
+                onTextLayout = onTextLayout,
+                overflow = overflow,
+                softWrap = softWrap,
+                maxLines = maxLines,
+                minLines = minLines,
+                fontFamilyResolver = LocalFontFamilyResolver.current,
+                placeholders = null,
+                onPlaceholderLayout = null,
+                selectionController = selectionController
+            ),
+            EmptyMeasurePolicy
+        )
+    } else {
+        // do the inline content allocs
+        val (placeholders, inlineComposables) = text.resolveInlineContent(inlineContent)
+        val measuredPlaceholderPositions = remember {
+            mutableStateOf<List<Rect?>?>(null)
+        }
+        Layout(
+            content = { InlineChildren(text, inlineComposables) },
+            modifier = modifier.textModifier(
+                text = text,
+                style = style,
+                onTextLayout = onTextLayout,
+                overflow = overflow,
+                softWrap = softWrap,
+                maxLines = maxLines,
+                minLines = minLines,
+                fontFamilyResolver = LocalFontFamilyResolver.current,
+                placeholders = placeholders,
+                onPlaceholderLayout = { measuredPlaceholderPositions.value = it },
+                selectionController = selectionController
+            ),
+            measurePolicy = TextMeasurePolicy { measuredPlaceholderPositions.value }
+        )
+    }
+}
+
+private object EmptyMeasurePolicy : MeasurePolicy {
+    private val placementBlock: Placeable.PlacementScope.() -> Unit = {}
+    override fun MeasureScope.measure(
+        measurables: List<Measurable>,
+        constraints: Constraints
+    ): MeasureResult {
+        return layout(constraints.maxWidth, constraints.maxHeight, placementBlock = placementBlock)
+    }
 }
 
 private class TextMeasurePolicy(
@@ -167,27 +223,38 @@
     minLines: Int,
     fontFamilyResolver: FontFamily.Resolver,
     placeholders: List<AnnotatedString.Range<Placeholder>>?,
-    onPlaceholderLayout: ((List<Rect?>) -> Unit)?
+    onPlaceholderLayout: ((List<Rect?>) -> Unit)?,
+    selectionController: SelectionController?
 ): Modifier {
-    val params = TextInlineContentLayoutDrawParams(
-        text,
-        style,
-        fontFamilyResolver,
-        onTextLayout,
-        overflow,
-        softWrap,
-        maxLines,
-        minLines,
-        placeholders,
-        onPlaceholderLayout,
-    )
-    return this then object : ModifierNodeElement<StaticTextModifier>(
-        params,
-        false,
-        debugInspectorInfo {}
-    ) {
-        override fun create(): StaticTextModifier = StaticTextModifier(params)
-        override fun update(node: StaticTextModifier): StaticTextModifier =
-            node.also { it.update(params) }
+    if (selectionController == null) {
+        val staticTextModifier = TextAnnotatedStringElement(
+            text,
+            style,
+            fontFamilyResolver,
+            onTextLayout,
+            overflow,
+            softWrap,
+            maxLines,
+            minLines,
+            placeholders,
+            onPlaceholderLayout,
+            null
+        )
+        return this then Modifier /* selection position */ then staticTextModifier
+    } else {
+        val selectableTextModifier = SelectableTextAnnotatedStringElement(
+            text,
+            style,
+            fontFamilyResolver,
+            onTextLayout,
+            overflow,
+            softWrap,
+            maxLines,
+            minLines,
+            placeholders,
+            onPlaceholderLayout,
+            selectionController
+        )
+        return this then selectionController.modifier then selectableTextModifier
     }
 }
diff --git a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/ContextMenu.kt
similarity index 61%
copy from tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
copy to compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/ContextMenu.kt
index ebac64e..b42ab4e 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/ContextMenu.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,10 +14,13 @@
  * limitations under the License.
  */
 
-package androidx.tv.material
+package androidx.compose.foundation.newtext.text.copypasta
 
-@RequiresOptIn(
-    "This tv-material API is experimental and likely to change or be removed in the future."
-)
-@Retention(AnnotationRetention.BINARY)
-annotation class ExperimentalTvMaterialApi
\ No newline at end of file
+import androidx.compose.foundation.newtext.text.copypasta.selection.SelectionManager
+import androidx.compose.runtime.Composable
+
+@Composable
+internal expect fun ContextMenuArea(
+    manager: SelectionManager,
+    content: @Composable () -> Unit
+)
\ No newline at end of file
diff --git a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/Expect.kt
similarity index 67%
copy from tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
copy to compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/Expect.kt
index ebac64e..ea9d587 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/Expect.kt
@@ -1,5 +1,7 @@
+// ktlint-disable filename
+
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,10 +16,10 @@
  * limitations under the License.
  */
 
-package androidx.tv.material
+package androidx.compose.foundation.newtext.text.copypasta
 
-@RequiresOptIn(
-    "This tv-material API is experimental and likely to change or be removed in the future."
-)
-@Retention(AnnotationRetention.BINARY)
-annotation class ExperimentalTvMaterialApi
\ No newline at end of file
+expect class AtomicLong(value: Long) {
+    fun get(): Long
+    fun set(value: Long)
+    fun getAndIncrement(): Long
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/LongPressTextDragObserver.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/LongPressTextDragObserver.kt
new file mode 100644
index 0000000..934fa91
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/LongPressTextDragObserver.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.newtext.text.copypasta
+
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.detectDragGestures
+import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.util.fastAny
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+internal interface TextDragObserver {
+    /**
+     * Called as soon as a down event is received. If the pointer eventually moves while remaining
+     * down, a drag gesture may be started. After this method:
+     * - [onUp] will always be called eventually, once the pointer is released.
+     * - [onStart] _may_ be called, if there is a drag that exceeds touch slop.
+     *
+     * This method will not be called before [onStart] in the case when a down event happens that
+     * may not result in a drag, e.g. on the down before a long-press that starts a selection.
+     */
+    fun onDown(point: Offset)
+
+    /**
+     * Called after [onDown] if an up event is received without dragging.
+     */
+    fun onUp()
+
+    /**
+     * Called once a drag gesture has started, which means touch slop has been exceeded.
+     * [onDown] _may_ be called before this method if the down event could not have
+     * started a different gesture.
+     */
+    fun onStart(startPoint: Offset)
+
+    fun onDrag(delta: Offset)
+
+    fun onStop()
+
+    fun onCancel()
+}
+
+internal suspend fun PointerInputScope.detectDragGesturesAfterLongPressWithObserver(
+    observer: TextDragObserver
+) = detectDragGesturesAfterLongPress(
+    onDragEnd = { observer.onStop() },
+    onDrag = { _, offset ->
+        observer.onDrag(offset)
+    },
+    onDragStart = {
+        observer.onStart(it)
+    },
+    onDragCancel = { observer.onCancel() }
+)
+
+/**
+ * Detects gesture events for a [TextDragObserver], including both initial down events and drag
+ * events.
+ */
+internal suspend fun PointerInputScope.detectDownAndDragGesturesWithObserver(
+    observer: TextDragObserver
+) {
+    coroutineScope {
+        launch {
+            detectPreDragGesturesWithObserver(observer)
+        }
+        launch {
+            detectDragGesturesWithObserver(observer)
+        }
+    }
+}
+
+/**
+ * Detects initial down events and calls [TextDragObserver.onDown] and
+ * [TextDragObserver.onUp].
+ */
+private suspend fun PointerInputScope.detectPreDragGesturesWithObserver(
+    observer: TextDragObserver
+) {
+    awaitEachGesture {
+        val down = awaitFirstDown()
+        observer.onDown(down.position)
+        // Wait for that pointer to come up.
+        do {
+            val event = awaitPointerEvent()
+        } while (event.changes.fastAny { it.id == down.id && it.pressed })
+        observer.onUp()
+    }
+}
+
+/**
+ * Detects drag gestures for a [TextDragObserver].
+ */
+private suspend fun PointerInputScope.detectDragGesturesWithObserver(
+    observer: TextDragObserver
+) {
+    detectDragGestures(
+        onDragEnd = { observer.onStop() },
+        onDrag = { _, offset ->
+            observer.onDrag(offset)
+        },
+        onDragStart = {
+            observer.onStart(it)
+        },
+        onDragCancel = { observer.onCancel() }
+    )
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/StringHelpers.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/StringHelpers.kt
new file mode 100644
index 0000000..a63f8bf
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/StringHelpers.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.newtext.text.copypasta
+
+import androidx.compose.ui.text.TextRange
+
+/**
+ * Returns the index of the character break preceding [index].
+ */
+internal expect fun String.findPrecedingBreak(index: Int): Int
+
+/**
+ * Returns the index of the character break following [index]. Returns -1 if there are no more
+ * breaks before the end of the string.
+ */
+internal expect fun String.findFollowingBreak(index: Int): Int
+
+internal fun CharSequence.findParagraphStart(startIndex: Int): Int {
+    for (index in startIndex - 1 downTo 1) {
+        if (this[index - 1] == '\n') {
+            return index
+        }
+    }
+    return 0
+}
+
+internal fun CharSequence.findParagraphEnd(startIndex: Int): Int {
+    for (index in startIndex + 1 until this.length) {
+        if (this[index] == '\n') {
+            return index
+        }
+    }
+    return this.length
+}
+
+/**
+ * Returns the text range of the paragraph at the given character offset.
+ *
+ * Paragraphs are separated by Line Feed character (\n).
+ */
+internal fun CharSequence.getParagraphBoundary(index: Int): TextRange {
+    return TextRange(findParagraphStart(index), findParagraphEnd(index))
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/TextLayoutResultProxy.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/TextLayoutResultProxy.kt
new file mode 100644
index 0000000..36d962b
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/TextLayoutResultProxy.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.compose.foundation.newtext.text.copypasta
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.text.TextLayoutResult
+
+internal class TextLayoutResultProxy(val value: TextLayoutResult) {
+    // TextLayoutResult methods
+    /**
+     * Translates the position of the touch on the screen to the position in text. Because touch
+     * is relative to the decoration box, we need to translate it to the inner text field's
+     * coordinates first before calculating position of the symbol in text.
+     *
+     * @param position original position of the gesture relative to the decoration box
+     * @param coerceInVisibleBounds if true and original [position] is outside visible bounds
+     * of the inner text field, the [position] will be shifted to the closest edge of the inner
+     * text field's visible bounds. This is useful when you have a decoration box
+     * bigger than the inner text field, so when user touches to the decoration box area, the cursor
+     * goes to the beginning or the end of the visible inner text field; otherwise if we put the
+     * cursor under the touch in the invisible part of the inner text field, it would scroll to
+     * make the cursor visible. This behavior is not needed, and therefore
+     * [coerceInVisibleBounds] should be set to false, when the user drags outside visible bounds
+     * to make a selection.
+     */
+    fun getOffsetForPosition(position: Offset, coerceInVisibleBounds: Boolean = true): Int {
+        val relativePosition = position
+            .let { if (coerceInVisibleBounds) it.coercedInVisibleBoundsOfInputText() else it }
+            .relativeToInputText()
+        return value.getOffsetForPosition(relativePosition)
+    }
+
+    fun getLineForVerticalPosition(vertical: Float): Int {
+        val relativeVertical = Offset(0f, vertical)
+            .coercedInVisibleBoundsOfInputText()
+            .relativeToInputText().y
+        return value.getLineForVerticalPosition(relativeVertical)
+    }
+
+    fun getLineEnd(lineIndex: Int, visibleEnd: Boolean = false): Int =
+        value.getLineEnd(lineIndex, visibleEnd)
+
+    /** Returns true if the screen coordinates position (x,y) corresponds to a character displayed
+     * in the view. Returns false when the position is in the empty space of left/right of text.
+     */
+    fun isPositionOnText(offset: Offset): Boolean {
+        val relativeOffset = offset.coercedInVisibleBoundsOfInputText().relativeToInputText()
+        val line = value.getLineForVerticalPosition(relativeOffset.y)
+        return relativeOffset.x >= value.getLineLeft(line) &&
+            relativeOffset.x <= value.getLineRight(line)
+    }
+
+    // Shift offset
+    /** Measured bounds of the decoration box and inner text field. Together used to
+     * calculate the relative touch offset. Because touches are applied on the decoration box, we
+     * need to translate it to the inner text field coordinates.
+     */
+    var innerTextFieldCoordinates: LayoutCoordinates? = null
+    var decorationBoxCoordinates: LayoutCoordinates? = null
+
+    /**
+     * Translates the click happened on the decoration box to the position in the inner text
+     * field coordinates. This relative position is then used to determine symbol position in
+     * text using TextLayoutResult object.
+     */
+    private fun Offset.relativeToInputText(): Offset {
+        // Translates touch to the inner text field coordinates
+        return innerTextFieldCoordinates?.let { innerTextFieldCoordinates ->
+            decorationBoxCoordinates?.let { decorationBoxCoordinates ->
+                if (innerTextFieldCoordinates.isAttached && decorationBoxCoordinates.isAttached) {
+                    innerTextFieldCoordinates.localPositionOf(decorationBoxCoordinates, this)
+                } else {
+                    this
+                }
+            }
+        } ?: this
+    }
+
+    /**
+     * If click on the decoration box happens outside visible inner text field, coerce the click
+     * position to the visible edges of the inner text field.
+     */
+    private fun Offset.coercedInVisibleBoundsOfInputText(): Offset {
+        // If offset is outside visible bounds of the inner text field, use visible bounds edges
+        val visibleInnerTextFieldRect =
+            innerTextFieldCoordinates?.let { innerTextFieldCoordinates ->
+                if (innerTextFieldCoordinates.isAttached) {
+                    decorationBoxCoordinates?.localBoundingBoxOf(innerTextFieldCoordinates)
+                } else {
+                    Rect.Zero
+                }
+            } ?: Rect.Zero
+        return this.coerceIn(visibleInnerTextFieldRect)
+    }
+}
+
+private fun Offset.coerceIn(rect: Rect): Offset {
+    val xOffset = when {
+        x < rect.left -> rect.left
+        x > rect.right -> rect.right
+        else -> x
+    }
+    val yOffset = when {
+        y < rect.top -> rect.top
+        y > rect.bottom -> rect.bottom
+        else -> y
+    }
+    return Offset(xOffset, yOffset)
+}
\ No newline at end of file
diff --git a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/TextPointerIcon.kt
similarity index 72%
copy from tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
copy to compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/TextPointerIcon.kt
index ebac64e..3d299bc 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/TextPointerIcon.kt
@@ -14,10 +14,8 @@
  * limitations under the License.
  */
 
-package androidx.tv.material
+package androidx.compose.foundation.newtext.text.copypasta
 
-@RequiresOptIn(
-    "This tv-material API is experimental and likely to change or be removed in the future."
-)
-@Retention(AnnotationRetention.BINARY)
-annotation class ExperimentalTvMaterialApi
\ No newline at end of file
+import androidx.compose.ui.input.pointer.PointerIcon
+
+internal expect val textPointerIcon: PointerIcon
\ No newline at end of file
diff --git a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/TouchMode.kt
similarity index 66%
copy from tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
copy to compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/TouchMode.kt
index ebac64e..54ee1ec 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/TouchMode.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * 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.
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package androidx.tv.material
+package androidx.compose.foundation.newtext.text.copypasta
 
-@RequiresOptIn(
-    "This tv-material API is experimental and likely to change or be removed in the future."
-)
-@Retention(AnnotationRetention.BINARY)
-annotation class ExperimentalTvMaterialApi
\ No newline at end of file
+/**
+ * This is a temporary workaround and should be removed after proper mouse handling is settled
+ * (b/171402426).
+ */
+internal val isInTouchMode: Boolean = true
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/MultiWidgetSelectionDelegate.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/MultiWidgetSelectionDelegate.kt
new file mode 100644
index 0000000..b234d34
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/MultiWidgetSelectionDelegate.kt
@@ -0,0 +1,255 @@
+/*
+ * 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.compose.foundation.newtext.text.copypasta.selection
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextRange
+import kotlin.math.max
+
+internal class MultiWidgetSelectionDelegate(
+    override val selectableId: Long,
+    private val coordinatesCallback: () -> LayoutCoordinates?,
+    private val layoutResultCallback: () -> TextLayoutResult?
+) : Selectable {
+
+    override fun updateSelection(
+        startHandlePosition: Offset,
+        endHandlePosition: Offset,
+        previousHandlePosition: Offset?,
+        isStartHandle: Boolean,
+        containerLayoutCoordinates: LayoutCoordinates,
+        adjustment: SelectionAdjustment,
+        previousSelection: Selection?
+    ): Pair<Selection?, Boolean> {
+        require(
+            previousSelection == null || (
+                selectableId == previousSelection.start.selectableId &&
+                    selectableId == previousSelection.end.selectableId
+                )
+        ) {
+            "The given previousSelection doesn't belong to this selectable."
+        }
+        val layoutCoordinates = getLayoutCoordinates() ?: return Pair(null, false)
+        val textLayoutResult = layoutResultCallback() ?: return Pair(null, false)
+
+        val relativePosition = containerLayoutCoordinates.localPositionOf(
+            layoutCoordinates, Offset.Zero
+        )
+        val localStartPosition = startHandlePosition - relativePosition
+        val localEndPosition = endHandlePosition - relativePosition
+        val localPreviousHandlePosition = previousHandlePosition?.let { it - relativePosition }
+
+        return getTextSelectionInfo(
+            textLayoutResult = textLayoutResult,
+            startHandlePosition = localStartPosition,
+            endHandlePosition = localEndPosition,
+            previousHandlePosition = localPreviousHandlePosition,
+            selectableId = selectableId,
+            adjustment = adjustment,
+            previousSelection = previousSelection,
+            isStartHandle = isStartHandle
+        )
+    }
+
+    override fun getSelectAllSelection(): Selection? {
+        val textLayoutResult = layoutResultCallback() ?: return null
+        val newSelectionRange = TextRange(0, textLayoutResult.layoutInput.text.length)
+
+        return getAssembledSelectionInfo(
+            newSelectionRange = newSelectionRange,
+            handlesCrossed = false,
+            selectableId = selectableId,
+            textLayoutResult = textLayoutResult
+        )
+    }
+
+    override fun getHandlePosition(selection: Selection, isStartHandle: Boolean): Offset {
+        // Check if the selection handle's selectable is the current selectable.
+        if (isStartHandle && selection.start.selectableId != this.selectableId ||
+            !isStartHandle && selection.end.selectableId != this.selectableId
+        ) {
+            return Offset.Zero
+        }
+
+        if (getLayoutCoordinates() == null) return Offset.Zero
+
+        val textLayoutResult = layoutResultCallback() ?: return Offset.Zero
+        return getSelectionHandleCoordinates(
+            textLayoutResult = textLayoutResult,
+            offset = if (isStartHandle) selection.start.offset else selection.end.offset,
+            isStart = isStartHandle,
+            areHandlesCrossed = selection.handlesCrossed
+        )
+    }
+
+    override fun getLayoutCoordinates(): LayoutCoordinates? {
+        val layoutCoordinates = coordinatesCallback()
+        if (layoutCoordinates == null || !layoutCoordinates.isAttached) return null
+        return layoutCoordinates
+    }
+
+    override fun getText(): AnnotatedString {
+        val textLayoutResult = layoutResultCallback() ?: return AnnotatedString("")
+        return textLayoutResult.layoutInput.text
+    }
+
+    override fun getBoundingBox(offset: Int): Rect {
+        val textLayoutResult = layoutResultCallback() ?: return Rect.Zero
+        val textLength = textLayoutResult.layoutInput.text.length
+        if (textLength < 1) return Rect.Zero
+        return textLayoutResult.getBoundingBox(
+            offset.coerceIn(0, textLength - 1)
+        )
+    }
+
+    override fun getRangeOfLineContaining(offset: Int): TextRange {
+        val textLayoutResult = layoutResultCallback() ?: return TextRange.Zero
+        val textLength = textLayoutResult.layoutInput.text.length
+        if (textLength < 1) return TextRange.Zero
+        val line = textLayoutResult.getLineForOffset(offset.coerceIn(0, textLength - 1))
+        return TextRange(
+            start = textLayoutResult.getLineStart(line),
+            end = textLayoutResult.getLineEnd(line, visibleEnd = true)
+        )
+    }
+}
+
+/**
+ * Return information about the current selection in the Text.
+ *
+ * @param textLayoutResult a result of the text layout.
+ * @param startHandlePosition The new positions of the moving selection handle.
+ * @param previousHandlePosition The old position of the moving selection handle since the last update.
+ * @param endHandlePosition the position of the selection handle that is not moving.
+ * @param selectableId the id of this [Selectable].
+ * @param adjustment the [SelectionAdjustment] used to process the raw selection range.
+ * @param previousSelection the previous text selection.
+ * @param isStartHandle whether the moving selection is the start selection handle.
+ *
+ * @return a pair consistent of updated [Selection] and a boolean representing whether the
+ * movement is consumed.
+ */
+internal fun getTextSelectionInfo(
+    textLayoutResult: TextLayoutResult,
+    startHandlePosition: Offset,
+    endHandlePosition: Offset,
+    previousHandlePosition: Offset?,
+    selectableId: Long,
+    adjustment: SelectionAdjustment,
+    previousSelection: Selection? = null,
+    isStartHandle: Boolean = true
+): Pair<Selection?, Boolean> {
+
+    val bounds = Rect(
+        0.0f,
+        0.0f,
+        textLayoutResult.size.width.toFloat(),
+        textLayoutResult.size.height.toFloat()
+    )
+
+    val isSelected =
+        SelectionMode.Vertical.isSelected(bounds, startHandlePosition, endHandlePosition)
+
+    if (!isSelected) {
+        return Pair(null, false)
+    }
+
+    val rawStartHandleOffset = getOffsetForPosition(textLayoutResult, bounds, startHandlePosition)
+    val rawEndHandleOffset = getOffsetForPosition(textLayoutResult, bounds, endHandlePosition)
+    val rawPreviousHandleOffset = previousHandlePosition?.let {
+        getOffsetForPosition(textLayoutResult, bounds, it)
+    } ?: -1
+
+    val adjustedTextRange = adjustment.adjust(
+        textLayoutResult = textLayoutResult,
+        newRawSelectionRange = TextRange(rawStartHandleOffset, rawEndHandleOffset),
+        previousHandleOffset = rawPreviousHandleOffset,
+        isStartHandle = isStartHandle,
+        previousSelectionRange = previousSelection?.toTextRange()
+    )
+    val newSelection = getAssembledSelectionInfo(
+        newSelectionRange = adjustedTextRange,
+        handlesCrossed = adjustedTextRange.reversed,
+        selectableId = selectableId,
+        textLayoutResult = textLayoutResult
+    )
+
+    // Determine whether the movement is consumed by this Selectable.
+    // If the selection has  changed, the movement is consumed.
+    // And there are also cases where the selection stays the same but selection handle raw
+    // offset has changed.(Usually this happen because of adjustment like SelectionAdjustment.Word)
+    // In this case we also consider the movement being consumed.
+    val selectionUpdated = newSelection != previousSelection
+    val handleUpdated = if (isStartHandle) {
+        rawStartHandleOffset != rawPreviousHandleOffset
+    } else {
+        rawEndHandleOffset != rawPreviousHandleOffset
+    }
+    val consumed = handleUpdated || selectionUpdated
+    return Pair(newSelection, consumed)
+}
+
+internal fun getOffsetForPosition(
+    textLayoutResult: TextLayoutResult,
+    bounds: Rect,
+    position: Offset
+): Int {
+    val length = textLayoutResult.layoutInput.text.length
+    return if (bounds.contains(position)) {
+        textLayoutResult.getOffsetForPosition(position).coerceIn(0, length)
+    } else {
+        val value = SelectionMode.Vertical.compare(position, bounds)
+        if (value < 0) 0 else length
+    }
+}
+
+/**
+ * [Selection] contains a lot of parameters. It looks more clean to assemble an object of this
+ * class in a separate method.
+ *
+ * @param newSelectionRange the final new selection text range.
+ * @param handlesCrossed true if the selection handles are crossed
+ * @param selectableId the id of the current [Selectable] for which the [Selection] is being
+ * calculated
+ * @param textLayoutResult a result of the text layout.
+ *
+ * @return an assembled object of [Selection] using the offered selection info.
+ */
+private fun getAssembledSelectionInfo(
+    newSelectionRange: TextRange,
+    handlesCrossed: Boolean,
+    selectableId: Long,
+    textLayoutResult: TextLayoutResult
+): Selection {
+    return Selection(
+        start = Selection.AnchorInfo(
+            direction = textLayoutResult.getBidiRunDirection(newSelectionRange.start),
+            offset = newSelectionRange.start,
+            selectableId = selectableId
+        ),
+        end = Selection.AnchorInfo(
+            direction = textLayoutResult.getBidiRunDirection(max(newSelectionRange.end - 1, 0)),
+            offset = newSelectionRange.end,
+            selectableId = selectableId
+        ),
+        handlesCrossed = handlesCrossed
+    )
+}
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/Selectable.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/Selectable.kt
new file mode 100644
index 0000000..c921a2d
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/Selectable.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.compose.foundation.newtext.text.copypasta.selection
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextRange
+
+/**
+ * Provides [Selection] information for a composable to SelectionContainer. Composables who can
+ * be selected should subscribe to [SelectionRegistrar] using this interface.
+ */
+
+internal interface Selectable {
+    /**
+     * An ID used by [SelectionRegistrar] to identify this [Selectable]. This value should not be
+     * [SelectionRegistrar.InvalidSelectableId].
+     * When a [Selectable] is created, it can request an ID from [SelectionRegistrar] by
+     * calling [SelectionRegistrar.nextSelectableId].
+     * @see SelectionRegistrar.nextSelectableId
+     */
+    val selectableId: Long
+
+    /**
+     * Updates the [Selection] information after a selection handle being moved. This method is
+     * expected to be called consecutively during the selection handle position update.
+     *
+     * @param startHandlePosition graphical position of the start selection handle
+     * @param endHandlePosition graphical position of the end selection handle
+     * @param previousHandlePosition the previous position of the moving selection handle
+     * @param containerLayoutCoordinates [LayoutCoordinates] of the composable
+     * @param adjustment [Selection] range is adjusted according to this param
+     * @param previousSelection previous selection result on this [Selectable]
+     * @param isStartHandle whether the moving selection handle is the start selection handle
+     *
+     * @throws IllegalStateException when the given [previousSelection] doesn't belong to this
+     * selectable. In other words, one of the [Selection.AnchorInfo] in the given
+     * [previousSelection] has a selectableId that doesn't match to the [selectableId] of this
+     * selectable.
+     * @return a pair consisting of the updated [Selection] and a boolean value representing
+     * whether the movement is consumed.
+     */
+    fun updateSelection(
+        startHandlePosition: Offset,
+        endHandlePosition: Offset,
+        previousHandlePosition: Offset?,
+        isStartHandle: Boolean = true,
+        containerLayoutCoordinates: LayoutCoordinates,
+        adjustment: SelectionAdjustment,
+        previousSelection: Selection? = null
+    ): Pair<Selection?, Boolean>
+
+    /**
+     * Returns selectAll [Selection] information for a selectable composable. If no selection can be
+     * provided null should be returned.
+     *
+     * @return selectAll [Selection] information for a selectable composable. If no selection can be
+     * provided null should be returned.
+     */
+    fun getSelectAllSelection(): Selection?
+
+    /**
+     * Return the [Offset] of a [SelectionHandle].
+     *
+     * @param selection [Selection] contains the [SelectionHandle]
+     * @param isStartHandle true if it's the start handle, false if it's the end handle.
+     *
+     * @return [Offset] of this handle, based on which the [SelectionHandle] will be drawn.
+     */
+    fun getHandlePosition(selection: Selection, isStartHandle: Boolean): Offset
+
+    /**
+     * Return the [LayoutCoordinates] of the [Selectable].
+     *
+     * @return [LayoutCoordinates] of the [Selectable]. This could be null if called before
+     * composing.
+     */
+    fun getLayoutCoordinates(): LayoutCoordinates?
+
+    /**
+     * Return the [AnnotatedString] of the [Selectable].
+     *
+     * @return text content as [AnnotatedString] of the [Selectable].
+     */
+    fun getText(): AnnotatedString
+
+    /**
+     * Return the bounding box of the character for given character offset. This is currently for
+     * text.
+     * In future when we implemented other selectable Composables, we can return the bounding box of
+     * the wanted rectangle. For example, for an image selectable, this should return the
+     * bounding box of the image.
+     *
+     * @param offset a character offset
+     * @return the bounding box for the character in [Rect], or [Rect.Zero] if the selectable is
+     * empty.
+     */
+    fun getBoundingBox(offset: Int): Rect
+
+    /**
+     * Return the offsets of the start and end of the line containing [offset], or [TextRange.Zero]
+     * if the selectable is empty. These offsets are in the same "coordinate space" as
+     * [getBoundingBox], and despite being returned in a [TextRange], may not refer to offsets in
+     * actual text if the selectable contains other types of content.
+     */
+    fun getRangeOfLineContaining(offset: Int): TextRange
+}
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/Selection.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/Selection.kt
new file mode 100644
index 0000000..5cedac2
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/Selection.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.compose.foundation.newtext.text.copypasta.selection
+
+import androidx.compose.runtime.Immutable
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.style.ResolvedTextDirection
+
+/**
+ * Information about the current Selection.
+ */
+@Immutable
+internal data class Selection(
+    /**
+     * Information about the start of the selection.
+     */
+    val start: AnchorInfo,
+
+    /**
+     * Information about the end of the selection.
+     */
+    val end: AnchorInfo,
+    /**
+     * The flag to show that the selection handles are dragged across each other. After selection
+     * is initialized, if user drags one handle to cross the other handle, this is true, otherwise
+     * it's false.
+     */
+    // If selection happens in single widget, checking [TextRange.start] > [TextRange.end] is
+    // enough.
+    // But when selection happens across multiple widgets, this value needs more complicated
+    // calculation. To avoid repeated calculation, making it as a flag is cheaper.
+    val handlesCrossed: Boolean = false
+) {
+    /**
+     * Contains information about an anchor (start/end) of selection.
+     */
+    @Immutable
+    internal data class AnchorInfo(
+        /**
+         * Text direction of the character in selection edge.
+         */
+        val direction: ResolvedTextDirection,
+
+        /**
+         * Character offset for the selection edge. This offset is within individual child text
+         * composable.
+         */
+        val offset: Int,
+
+        /**
+         * The id of the [Selectable] which contains this [Selection] Anchor.
+         */
+        val selectableId: Long
+    )
+
+    fun merge(other: Selection?): Selection {
+        if (other == null) return this
+
+        var selection = this
+        selection = if (handlesCrossed) {
+            selection.copy(start = other.start)
+        } else {
+            selection.copy(end = other.end)
+        }
+
+        return selection
+    }
+
+    /**
+     * Returns the selection offset information as a [TextRange]
+     */
+    fun toTextRange(): TextRange {
+        return TextRange(start.offset, end.offset)
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionAdjustment.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionAdjustment.kt
new file mode 100644
index 0000000..c53cf0a
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionAdjustment.kt
@@ -0,0 +1,466 @@
+/*
+ * 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.compose.foundation.newtext.text.copypasta.selection
+
+import androidx.compose.foundation.newtext.text.copypasta.getParagraphBoundary
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextRange
+
+/**
+ * Selection can be adjusted depends on context. For example, in touch mode dragging after a long
+ * press adjusts selection by word. But selection by dragging handles is character precise
+ * without adjustments. With a mouse, double-click selects by words and triple-clicks by paragraph.
+ * @see [SelectionRegistrar.notifySelectionUpdate]
+ */
+internal interface SelectionAdjustment {
+
+    /**
+     * The callback function that is called once a new selection arrives, the return value of
+     * this function will be the final selection range on the corresponding [Selectable].
+     *
+     * @param textLayoutResult the [TextLayoutResult] of the involved [Selectable].
+     * @param newRawSelectionRange the new selection range computed from the selection handle
+     * position on screen.
+     * @param previousHandleOffset the previous offset of the moving handle. When isStartHandle is
+     * true, it's the previous offset of the start handle before the movement, and vice versa.
+     * When there isn't a valid previousHandleOffset, previousHandleOffset should be -1.
+     * @param isStartHandle whether the moving handle is the start handle.
+     * @param previousSelectionRange the previous selection range, or the selection range to be
+     * updated.
+     */
+    fun adjust(
+        textLayoutResult: TextLayoutResult,
+        newRawSelectionRange: TextRange,
+        previousHandleOffset: Int,
+        isStartHandle: Boolean,
+        previousSelectionRange: TextRange?
+    ): TextRange
+
+    companion object {
+        /**
+         * The selection adjustment that does nothing and directly return the input raw
+         * selection range.
+         */
+        val None = object : SelectionAdjustment {
+            override fun adjust(
+                textLayoutResult: TextLayoutResult,
+                newRawSelectionRange: TextRange,
+                previousHandleOffset: Int,
+                isStartHandle: Boolean,
+                previousSelectionRange: TextRange?
+            ): TextRange = newRawSelectionRange
+        }
+
+        /**
+         * The character based selection. It normally won't change the raw selection range except
+         * when the input raw selection range is collapsed. In this case, it will always make
+         * sure at least one character is selected.
+         * When the given raw selection range is collapsed:
+         * a) it will always try to adjust the changing selection boundary(base on the value of
+         * isStartHandle) and makes sure the other boundary remains the same after the adjustment
+         * b) if the previous selection range is reversed, it will try to make the adjusted
+         * selection range reversed as well, and vice versa.
+         */
+        val Character = object : SelectionAdjustment {
+            override fun adjust(
+                textLayoutResult: TextLayoutResult,
+                newRawSelectionRange: TextRange,
+                previousHandleOffset: Int,
+                isStartHandle: Boolean,
+                previousSelectionRange: TextRange?
+            ): TextRange {
+                return if (newRawSelectionRange.collapsed) {
+                    // If there isn't any selection before, we assume handles are not crossed.
+                    val previousHandlesCrossed = previousSelectionRange?.reversed ?: false
+                    ensureAtLeastOneChar(
+                        offset = newRawSelectionRange.start,
+                        lastOffset = textLayoutResult.layoutInput.text.lastIndex,
+                        isStartHandle = isStartHandle,
+                        previousHandlesCrossed = previousHandlesCrossed
+                    )
+                } else {
+                    newRawSelectionRange
+                }
+            }
+        }
+
+        /**
+         * The word based selection adjustment. It will adjust the raw input selection such that
+         * the selection boundary snap to the word boundary. It will always expand the raw input
+         * selection range to the closest word boundary. If the raw selection is reversed, it
+         * will always return a reversed selection, and vice versa.
+         */
+        val Word = object : SelectionAdjustment {
+            override fun adjust(
+                textLayoutResult: TextLayoutResult,
+                newRawSelectionRange: TextRange,
+                previousHandleOffset: Int,
+                isStartHandle: Boolean,
+                previousSelectionRange: TextRange?
+            ): TextRange {
+                return adjustByBoundary(
+                    textLayoutResult = textLayoutResult,
+                    newRawSelection = newRawSelectionRange,
+                    boundaryFun = textLayoutResult::getWordBoundary
+                )
+            }
+        }
+
+        /**
+         * The paragraph based selection adjustment. It will adjust the raw input selection such
+         * that the selection boundary snap to the paragraph boundary. It will always expand the
+         * raw input selection range to the closest paragraph boundary. If the raw selection is
+         * reversed, it will always return a reversed selection, and vice versa.
+         */
+        val Paragraph = object : SelectionAdjustment {
+            override fun adjust(
+                textLayoutResult: TextLayoutResult,
+                newRawSelectionRange: TextRange,
+                previousHandleOffset: Int,
+                isStartHandle: Boolean,
+                previousSelectionRange: TextRange?
+            ): TextRange {
+                val boundaryFun = textLayoutResult.layoutInput.text::getParagraphBoundary
+                return adjustByBoundary(
+                    textLayoutResult = textLayoutResult,
+                    newRawSelection = newRawSelectionRange,
+                    boundaryFun = boundaryFun
+                )
+            }
+        }
+
+        private fun adjustByBoundary(
+            textLayoutResult: TextLayoutResult,
+            newRawSelection: TextRange,
+            boundaryFun: (Int) -> TextRange
+        ): TextRange {
+            if (textLayoutResult.layoutInput.text.isEmpty()) {
+                return TextRange.Zero
+            }
+            val maxOffset = textLayoutResult.layoutInput.text.lastIndex
+            val startBoundary = boundaryFun(newRawSelection.start.coerceIn(0, maxOffset))
+            val endBoundary = boundaryFun(newRawSelection.end.coerceIn(0, maxOffset))
+
+            // If handles are not crossed, start should be snapped to the start of the word
+            // containing the start offset, and end should be snapped to the end of the word
+            // containing the end offset. If handles are crossed, start should be snapped to the
+            // end of the word containing the start offset, and end should be snapped to the start
+            // of the word containing the end offset.
+            val start = if (newRawSelection.reversed) startBoundary.end else startBoundary.start
+            val end = if (newRawSelection.reversed) endBoundary.start else endBoundary.end
+            return TextRange(start, end)
+        }
+
+        /**
+         * A special version of character based selection that accelerates the selection update
+         * with word based selection. In short, it expands by word and shrinks by character.
+         * Here is more details of the behavior:
+         * 1. When previous selection is null, it will use word based selection.
+         * 2. When the start/end offset has moved to a different line, it will use word
+         * based selection.
+         * 3. When the selection is shrinking, it behave same as the character based selection.
+         * Shrinking means that the start/end offset is moving in the direction that makes
+         * selected text shorter.
+         * 4. The selection boundary is expanding,
+         *  a.if the previous start/end offset is not a word boundary, use character based
+         * selection.
+         *  b.if the previous start/end offset is a word boundary, use word based selection.
+         *
+         *  Notice that this selection adjustment assumes that when isStartHandle is true, only
+         *  start handle is moving(or unchanged), and vice versa.
+         */
+        val CharacterWithWordAccelerate = object : SelectionAdjustment {
+            override fun adjust(
+                textLayoutResult: TextLayoutResult,
+                newRawSelectionRange: TextRange,
+                previousHandleOffset: Int,
+                isStartHandle: Boolean,
+                previousSelectionRange: TextRange?
+            ): TextRange {
+                // Previous selection is null. We start a word based selection.
+                if (previousSelectionRange == null) {
+                    return Word.adjust(
+                        textLayoutResult = textLayoutResult,
+                        newRawSelectionRange = newRawSelectionRange,
+                        previousHandleOffset = previousHandleOffset,
+                        isStartHandle = isStartHandle,
+                        previousSelectionRange = previousSelectionRange
+                    )
+                }
+
+                // The new selection is collapsed, ensure at least one char is selected.
+                if (newRawSelectionRange.collapsed) {
+                    return ensureAtLeastOneChar(
+                        offset = newRawSelectionRange.start,
+                        lastOffset = textLayoutResult.layoutInput.text.lastIndex,
+                        isStartHandle = isStartHandle,
+                        previousHandlesCrossed = previousSelectionRange.reversed
+                    )
+                }
+
+                val start: Int
+                val end: Int
+                if (isStartHandle) {
+                    start = updateSelectionBoundary(
+                        textLayoutResult = textLayoutResult,
+                        newRawOffset = newRawSelectionRange.start,
+                        previousRawOffset = previousHandleOffset,
+                        previousAdjustedOffset = previousSelectionRange.start,
+                        otherBoundaryOffset = newRawSelectionRange.end,
+                        isStart = true,
+                        isReversed = newRawSelectionRange.reversed
+                    )
+                    end = newRawSelectionRange.end
+                } else {
+                    start = newRawSelectionRange.start
+                    end = updateSelectionBoundary(
+                        textLayoutResult = textLayoutResult,
+                        newRawOffset = newRawSelectionRange.end,
+                        previousRawOffset = previousHandleOffset,
+                        previousAdjustedOffset = previousSelectionRange.end,
+                        otherBoundaryOffset = newRawSelectionRange.start,
+                        isStart = false,
+                        isReversed = newRawSelectionRange.reversed
+                    )
+                }
+                return TextRange(start, end)
+            }
+
+            /**
+             * Helper function that updates start or end boundary of the selection. It implements
+             * the "expand by word and shrink by character behavior".
+             *
+             * @param textLayoutResult the text layout result
+             * @param newRawOffset the new raw offset of the selection boundary after the movement.
+             * @param previousRawOffset the raw offset of the updated selection boundary before the
+             * movement. In the case where previousRawOffset invalid(when selection update is
+             * triggered by long-press or click) pass -1 for this parameter.
+             * @param previousAdjustedOffset the previous final/adjusted offset. It's the current
+             * @param otherBoundaryOffset the offset of the other selection boundary. It is used
+             * to avoid empty selection in word based selection mode.
+             * selection boundary.
+             * @param isStart whether it's updating the selection start or end boundary.
+             * @param isReversed whether the selection is reversed or not. We use
+             * this information to determine if the selection is expanding or shrinking.
+             */
+            private fun updateSelectionBoundary(
+                textLayoutResult: TextLayoutResult,
+                newRawOffset: Int,
+                previousRawOffset: Int,
+                previousAdjustedOffset: Int,
+                otherBoundaryOffset: Int,
+                isStart: Boolean,
+                isReversed: Boolean
+            ): Int {
+                // The raw offset didn't change, directly return the previous adjusted start offset.
+                if (newRawOffset == previousRawOffset) {
+                    return previousAdjustedOffset
+                }
+
+                val currentLine = textLayoutResult.getLineForOffset(newRawOffset)
+                val previousLine = textLayoutResult.getLineForOffset(previousAdjustedOffset)
+
+                // The updating selection boundary has crossed a line, use word based selection.
+                if (currentLine != previousLine) {
+                    return snapToWordBoundary(
+                        textLayoutResult = textLayoutResult,
+                        newRawOffset = newRawOffset,
+                        currentLine = currentLine,
+                        otherBoundaryOffset = otherBoundaryOffset,
+                        isStart = isStart,
+                        isReversed = isReversed
+                    )
+                }
+
+                // Check if the start or end selection boundary is expanding. If it's shrinking,
+                // use character based selection.
+                val isExpanding =
+                    isExpanding(newRawOffset, previousRawOffset, isStart, isReversed)
+                if (!isExpanding) {
+                    return newRawOffset
+                }
+
+                // If the previous start/end offset is not at a word boundary, which is indicating
+                // that start/end offset is updating within a word. In this case, it still uses
+                // character based selection.
+                if (!textLayoutResult.isAtWordBoundary(previousAdjustedOffset)) {
+                    return newRawOffset
+                }
+
+                // At this point we know, the updating start/end offset is still in the same line,
+                // it's expanding the selection, and it's not updating within a word. It should
+                // use word based selection.
+                return snapToWordBoundary(
+                    textLayoutResult = textLayoutResult,
+                    newRawOffset = newRawOffset,
+                    currentLine = currentLine,
+                    otherBoundaryOffset = otherBoundaryOffset,
+                    isStart = isStart,
+                    isReversed = isReversed
+                )
+            }
+
+            private fun snapToWordBoundary(
+                textLayoutResult: TextLayoutResult,
+                newRawOffset: Int,
+                currentLine: Int,
+                otherBoundaryOffset: Int,
+                isStart: Boolean,
+                isReversed: Boolean
+            ): Int {
+                val wordBoundary = textLayoutResult.getWordBoundary(newRawOffset)
+
+                // In the case where the target word crosses multiple lines due to hyphenation or
+                // being too long, we use the line start/end to keep the adjusted offset at the
+                // same line.
+                val wordStartLine = textLayoutResult.getLineForOffset(wordBoundary.start)
+                val start = if (wordStartLine == currentLine) {
+                    wordBoundary.start
+                } else {
+                    textLayoutResult.getLineStart(currentLine)
+                }
+
+                val wordEndLine = textLayoutResult.getLineForOffset(wordBoundary.end)
+                val end = if (wordEndLine == currentLine) {
+                    wordBoundary.end
+                } else {
+                    textLayoutResult.getLineEnd(currentLine)
+                }
+
+                // If one of the word boundary is exactly same as the otherBoundaryOffset, we
+                // can't snap to this word boundary since it will result in an empty selection
+                // range.
+                if (start == otherBoundaryOffset) {
+                    return end
+                }
+                if (end == otherBoundaryOffset) {
+                    return start
+                }
+
+                val threshold = (start + end) / 2
+                return if (isStart xor isReversed) {
+                    // In this branch when:
+                    // 1. selection is updating the start offset, and selection is not reversed.
+                    // 2. selection is updating the end offset, and selection is reversed.
+                    if (newRawOffset <= threshold) {
+                        start
+                    } else {
+                        end
+                    }
+                } else {
+                    // In this branch when:
+                    // 1. selection is updating the end offset, and selection is not reversed.
+                    // 2. selection is updating the start offset, and selection is reversed.
+                    if (newRawOffset >= threshold) {
+                        end
+                    } else {
+                        start
+                    }
+                }
+            }
+
+            private fun TextLayoutResult.isAtWordBoundary(offset: Int): Boolean {
+                val wordBoundary = getWordBoundary(offset)
+                return offset == wordBoundary.start || offset == wordBoundary.end
+            }
+
+            private fun isExpanding(
+                newRawOffset: Int,
+                previousRawOffset: Int,
+                isStart: Boolean,
+                previousReversed: Boolean
+            ): Boolean {
+                // -1 is considered as no previous offset, so the selection is expanding.
+                if (previousRawOffset == -1) {
+                    return true
+                }
+                if (newRawOffset == previousRawOffset) {
+                    return false
+                }
+                return if (isStart xor previousReversed) {
+                    newRawOffset < previousRawOffset
+                } else {
+                    newRawOffset > previousRawOffset
+                }
+            }
+        }
+    }
+}
+
+/**
+ * This method adjusts the raw start and end offset and bounds the selection to one character. The
+ * logic of bounding evaluates the last selection result, which handle is being dragged, and if
+ * selection reaches the boundary.
+ *
+ * @param offset unprocessed start and end offset calculated directly from input position, in
+ * this case start and offset equals to each other.
+ * @param lastOffset last offset of the text. It's actually the length of the text.
+ * @param isStartHandle true if the start handle is being dragged
+ * @param previousHandlesCrossed true if the selection handles are crossed in the previous
+ * selection. This function will try to maintain the handle cross state. This can help make
+ * selection stable.
+ *
+ * @return the adjusted [TextRange].
+ */
+internal fun ensureAtLeastOneChar(
+    offset: Int,
+    lastOffset: Int,
+    isStartHandle: Boolean,
+    previousHandlesCrossed: Boolean
+): TextRange {
+    // When lastOffset is 0, it can only return an empty TextRange.
+    // When previousSelection is null, it won't start a selection and return an empty TextRange.
+    if (lastOffset == 0) return TextRange(offset, offset)
+
+    // When offset is at the boundary, the handle that is not dragged should be at [offset]. Here
+    // the other handle's position is computed accordingly.
+    if (offset == 0) {
+        return if (isStartHandle) {
+            TextRange(1, 0)
+        } else {
+            TextRange(0, 1)
+        }
+    }
+
+    if (offset == lastOffset) {
+        return if (isStartHandle) {
+            TextRange(lastOffset - 1, lastOffset)
+        } else {
+            TextRange(lastOffset, lastOffset - 1)
+        }
+    }
+
+    // In other cases, this function will try to maintain the current cross handle states.
+    // Only in this way the selection can be stable.
+    return if (isStartHandle) {
+        if (!previousHandlesCrossed) {
+            // Handle is NOT crossed, and the start handle is dragged.
+            TextRange(offset - 1, offset)
+        } else {
+            // Handle is crossed, and the start handle is dragged.
+            TextRange(offset + 1, offset)
+        }
+    } else {
+        if (!previousHandlesCrossed) {
+            // Handle is NOT crossed, and the end handle is dragged.
+            TextRange(offset, offset + 1)
+        } else {
+            // Handle is crossed, and the end handle is dragged.
+            TextRange(offset, offset - 1)
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionContainer.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionContainer.kt
new file mode 100644
index 0000000..82d53aa
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionContainer.kt
@@ -0,0 +1,143 @@
+/*
+ * 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.compose.foundation.newtext.text.copypasta.selection
+
+import androidx.compose.foundation.newtext.text.copypasta.ContextMenuArea
+import androidx.compose.foundation.newtext.text.copypasta.detectDownAndDragGesturesWithObserver
+import androidx.compose.foundation.newtext.text.copypasta.isInTouchMode
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.platform.LocalHapticFeedback
+import androidx.compose.ui.platform.LocalTextToolbar
+import androidx.compose.ui.util.fastForEach
+
+/**
+ * Enables text selection for its direct or indirect children.
+ *
+ * @sample androidx.compose.foundation.samples.SelectionSample
+ */
+@Composable
+fun SelectionContainer(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
+    var selection by remember { mutableStateOf<Selection?>(null) }
+    SelectionContainer(
+        modifier = modifier,
+        selection = selection,
+        onSelectionChange = {
+            selection = it
+        },
+        children = content
+    )
+}
+
+/**
+ * Disables text selection for its direct or indirect children. To use this, simply add this
+ * to wrap one or more text composables.
+ *
+ * @sample androidx.compose.foundation.samples.DisableSelectionSample
+ */
+@Composable
+fun DisableSelection(content: @Composable () -> Unit) {
+    CompositionLocalProvider(
+        LocalSelectionRegistrar provides null,
+        content = content
+    )
+}
+
+/**
+ * Selection Composable.
+ *
+ * The selection composable wraps composables and let them to be selectable. It paints the selection
+ * area with start and end handles.
+ */
+@Suppress("ComposableLambdaParameterNaming")
+@Composable
+internal fun SelectionContainer(
+    /** A [Modifier] for SelectionContainer. */
+    modifier: Modifier = Modifier,
+    /** Current Selection status.*/
+    selection: Selection?,
+    /** A function containing customized behaviour when selection changes. */
+    onSelectionChange: (Selection?) -> Unit,
+    children: @Composable () -> Unit
+) {
+    val registrarImpl = remember { SelectionRegistrarImpl() }
+    val manager = remember { SelectionManager(registrarImpl) }
+
+    manager.hapticFeedBack = LocalHapticFeedback.current
+    manager.clipboardManager = LocalClipboardManager.current
+    manager.textToolbar = LocalTextToolbar.current
+    manager.onSelectionChange = onSelectionChange
+    manager.selection = selection
+    manager.touchMode = isInTouchMode
+
+    ContextMenuArea(manager) {
+        CompositionLocalProvider(LocalSelectionRegistrar provides registrarImpl) {
+            // Get the layout coordinates of the selection container. This is for hit test of
+            // cross-composable selection.
+            SimpleLayout(modifier = modifier.then(manager.modifier)) {
+                children()
+                if (isInTouchMode && manager.hasFocus) {
+                    manager.selection?.let {
+                        listOf(true, false).fastForEach { isStartHandle ->
+                            val observer = remember(isStartHandle) {
+                                manager.handleDragObserver(isStartHandle)
+                            }
+                            val position = if (isStartHandle) {
+                                manager.startHandlePosition
+                            } else {
+                                manager.endHandlePosition
+                            }
+
+                            val direction = if (isStartHandle) {
+                                it.start.direction
+                            } else {
+                                it.end.direction
+                            }
+
+                            if (position != null) {
+                                SelectionHandle(
+                                    position = position,
+                                    isStartHandle = isStartHandle,
+                                    direction = direction,
+                                    handlesCrossed = it.handlesCrossed,
+                                    modifier = Modifier.pointerInput(observer) {
+                                        detectDownAndDragGesturesWithObserver(observer)
+                                    },
+                                    content = null
+                                )
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    DisposableEffect(manager) {
+        onDispose {
+            manager.hideSelectionToolbar()
+        }
+    }
+}
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionHandles.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionHandles.kt
new file mode 100644
index 0000000..ddea64a
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionHandles.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.compose.foundation.newtext.text.copypasta.selection
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.semantics.SemanticsPropertyKey
+import androidx.compose.ui.text.style.ResolvedTextDirection
+import androidx.compose.ui.unit.dp
+
+internal val HandleWidth = 25.dp
+internal val HandleHeight = 25.dp
+
+/**
+ * [SelectionHandleInfo]s for the nodes representing selection handles. These nodes are in popup
+ * windows, and will respond to drag gestures.
+ */
+internal val SelectionHandleInfoKey =
+    SemanticsPropertyKey<SelectionHandleInfo>("SelectionHandleInfo")
+
+/**
+ * Information about a single selection handle popup.
+ *
+ * @param position The position that the handle is anchored to relative to the selectable content.
+ * This position is not necessarily the position of the popup itself, it's the position that the
+ * handle "points" to
+ */
+internal data class SelectionHandleInfo(
+    // TODO: This is removed for copypasta because it's never used in basic usage
+    // val handle: Handle,
+    val position: Offset
+)
+
+@Composable
+internal expect fun SelectionHandle(
+    position: Offset,
+    isStartHandle: Boolean,
+    direction: ResolvedTextDirection,
+    handlesCrossed: Boolean,
+    modifier: Modifier,
+    content: @Composable (() -> Unit)?
+)
+
+/**
+ * Adjust coordinates for given text offset.
+ *
+ * Currently [android.text.Layout.getLineBottom] returns y coordinates of the next
+ * line's top offset, which is not included in current line's hit area. To be able to
+ * hit current line, move up this y coordinates by 1 pixel.
+ */
+internal fun getAdjustedCoordinates(position: Offset): Offset {
+    return Offset(position.x, position.y - 1f)
+}
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionMagnifier.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionMagnifier.kt
new file mode 100644
index 0000000..2817f3e
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionMagnifier.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.compose.foundation.newtext.text.copypasta.selection
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationVector2D
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.SpringSpec
+import androidx.compose.animation.core.TwoWayConverter
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.isSpecified
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+private val UnspecifiedAnimationVector2D = AnimationVector2D(Float.NaN, Float.NaN)
+
+/** Like `Offset.VectorConverter` but propagates [Offset.Unspecified] values. */
+private val UnspecifiedSafeOffsetVectorConverter = TwoWayConverter<Offset, AnimationVector2D>(
+    convertToVector = {
+        if (it.isSpecified) {
+            AnimationVector2D(it.x, it.y)
+        } else {
+            UnspecifiedAnimationVector2D
+        }
+    },
+    convertFromVector = { Offset(it.v1, it.v2) }
+)
+
+private val OffsetDisplacementThreshold = Offset(
+    Spring.DefaultDisplacementThreshold,
+    Spring.DefaultDisplacementThreshold
+)
+
+private val MagnifierSpringSpec = SpringSpec(visibilityThreshold = OffsetDisplacementThreshold)
+
+/**
+ * The text magnifier follows horizontal dragging exactly, but is vertically clamped to the current
+ * line, so when it changes lines we animate it.
+ */
+@Suppress("ModifierInspectorInfo")
+internal fun Modifier.animatedSelectionMagnifier(
+    magnifierCenter: () -> Offset,
+    platformMagnifier: (animatedCenter: () -> Offset) -> Modifier
+): Modifier = composed {
+    val animatedCenter by rememberAnimatedMagnifierPosition(targetCalculation = magnifierCenter)
+    return@composed platformMagnifier { animatedCenter }
+}
+
+/**
+ * Remembers and returns a [State] that will smoothly animate to the result of [targetCalculation]
+ * any time the result of [targetCalculation] changes due to any state values it reads change.
+ */
+@Composable
+private fun rememberAnimatedMagnifierPosition(
+    targetCalculation: () -> Offset,
+): State<Offset> {
+    val targetValue by remember { derivedStateOf(targetCalculation) }
+    val animatable = remember {
+        // Can't use Offset.VectorConverter because we need to handle Unspecified specially.
+        Animatable(targetValue, UnspecifiedSafeOffsetVectorConverter, OffsetDisplacementThreshold)
+    }
+    LaunchedEffect(Unit) {
+        val animationScope = this
+        snapshotFlow { targetValue }
+            .collect { targetValue ->
+                // Only animate the position when moving vertically (i.e. jumping between lines),
+                // since horizontal movement in a single line should stay as close to the gesture as
+                // possible and animation would only add unnecessary lag.
+                if (
+                    animatable.value.isSpecified &&
+                    targetValue.isSpecified &&
+                    animatable.value.y != targetValue.y
+                ) {
+                    // Launch the animation, instead of cancelling and re-starting manually via
+                    // collectLatest, so if another animation is started before this one finishes,
+                    // the new one will use the correct velocity, e.g. in order to propagate spring
+                    // inertia.
+                    animationScope.launch {
+                        animatable.animateTo(targetValue, MagnifierSpringSpec)
+                    }
+                } else {
+                    animatable.snapTo(targetValue)
+                }
+            }
+    }
+    return animatable.asState()
+}
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionManager.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionManager.kt
new file mode 100644
index 0000000..73e0612
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionManager.kt
@@ -0,0 +1,903 @@
+/*
+ * 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.compose.foundation.newtext.text.copypasta.selection
+
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.waitForUpOrCancellation
+import androidx.compose.foundation.newtext.text.copypasta.TextDragObserver
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.hapticfeedback.HapticFeedback
+import androidx.compose.ui.hapticfeedback.HapticFeedbackType
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.input.key.onKeyEvent
+import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.boundsInWindow
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.platform.ClipboardManager
+import androidx.compose.ui.platform.TextToolbar
+import androidx.compose.ui.platform.TextToolbarStatus
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.unit.IntSize
+import kotlin.math.absoluteValue
+import kotlin.math.max
+import kotlin.math.min
+
+/**
+ * A bridge class between user interaction to the text composables for text selection.
+ */
+internal class SelectionManager(private val selectionRegistrar: SelectionRegistrarImpl) {
+
+    private val _selection: MutableState<Selection?> = mutableStateOf(null)
+
+    /**
+     * The current selection.
+     */
+    var selection: Selection?
+        get() = _selection.value
+        set(value) {
+            _selection.value = value
+            if (value != null) {
+                updateHandleOffsets()
+            }
+        }
+
+    /**
+     * Is touch mode active
+     */
+    var touchMode: Boolean = true
+
+    /**
+     * The manager will invoke this every time it comes to the conclusion that the selection should
+     * change. The expectation is that this callback will end up causing `setSelection` to get
+     * called. This is what makes this a "controlled component".
+     */
+    var onSelectionChange: (Selection?) -> Unit = {}
+
+    /**
+     * [HapticFeedback] handle to perform haptic feedback.
+     */
+    var hapticFeedBack: HapticFeedback? = null
+
+    /**
+     * [ClipboardManager] to perform clipboard features.
+     */
+    var clipboardManager: ClipboardManager? = null
+
+    /**
+     * [TextToolbar] to show floating toolbar(post-M) or primary toolbar(pre-M).
+     */
+    var textToolbar: TextToolbar? = null
+
+    /**
+     * Focus requester used to request focus when selection becomes active.
+     */
+    var focusRequester: FocusRequester = FocusRequester()
+
+    /**
+     * Return true if the corresponding SelectionContainer is focused.
+     */
+    var hasFocus: Boolean by mutableStateOf(false)
+
+    /**
+     * Modifier for selection container.
+     */
+    val modifier
+        get() = Modifier
+            .onClearSelectionRequested { onRelease() }
+            .onGloballyPositioned { containerLayoutCoordinates = it }
+            .focusRequester(focusRequester)
+            .onFocusChanged { focusState ->
+                if (!focusState.isFocused && hasFocus) {
+                    onRelease()
+                }
+                hasFocus = focusState.isFocused
+            }
+            .focusable()
+            .onKeyEvent {
+                if (isCopyKeyEvent(it)) {
+                    copy()
+                    true
+                } else {
+                    false
+                }
+            }
+            .then(if (shouldShowMagnifier) Modifier.selectionMagnifier(this) else Modifier)
+
+    private var previousPosition: Offset? = null
+
+    /**
+     * Layout Coordinates of the selection container.
+     */
+    var containerLayoutCoordinates: LayoutCoordinates? = null
+        set(value) {
+            field = value
+            if (hasFocus && selection != null) {
+                val positionInWindow = value?.positionInWindow()
+                if (previousPosition != positionInWindow) {
+                    previousPosition = positionInWindow
+                    updateHandleOffsets()
+                    updateSelectionToolbarPosition()
+                }
+            }
+        }
+
+    /**
+     * The beginning position of the drag gesture. Every time a new drag gesture starts, it wil be
+     * recalculated.
+     */
+    internal var dragBeginPosition by mutableStateOf(Offset.Zero)
+        private set
+
+    /**
+     * The total distance being dragged of the drag gesture. Every time a new drag gesture starts,
+     * it will be zeroed out.
+     */
+    internal var dragTotalDistance by mutableStateOf(Offset.Zero)
+        private set
+
+    /**
+     * The calculated position of the start handle in the [SelectionContainer] coordinates. It
+     * is null when handle shouldn't be displayed.
+     * It is a [State] so reading it during the composition will cause recomposition every time
+     * the position has been changed.
+     */
+    var startHandlePosition: Offset? by mutableStateOf(null)
+        private set
+
+    /**
+     * The calculated position of the end handle in the [SelectionContainer] coordinates. It
+     * is null when handle shouldn't be displayed.
+     * It is a [State] so reading it during the composition will cause recomposition every time
+     * the position has been changed.
+     */
+    var endHandlePosition: Offset? by mutableStateOf(null)
+        private set
+
+    /**
+     * The handle that is currently being dragged, or null when no handle is being dragged. To get
+     * the position of the last drag event, use [currentDragPosition].
+     */
+    var draggingHandle: Handle? by mutableStateOf(null)
+        private set
+
+    /**
+     * When a handle is being dragged (i.e. [draggingHandle] is non-null), this is the last position
+     * of the actual drag event. It is not clamped to handle positions. Null when not being dragged.
+     */
+    var currentDragPosition: Offset? by mutableStateOf(null)
+        private set
+
+    private val shouldShowMagnifier get() = draggingHandle != null
+
+    init {
+        selectionRegistrar.onPositionChangeCallback = { selectableId ->
+            if (
+                selectableId == selection?.start?.selectableId ||
+                selectableId == selection?.end?.selectableId
+            ) {
+                updateHandleOffsets()
+                updateSelectionToolbarPosition()
+            }
+        }
+
+        selectionRegistrar.onSelectionUpdateStartCallback =
+            { layoutCoordinates, position, selectionMode ->
+                val positionInContainer = convertToContainerCoordinates(
+                    layoutCoordinates,
+                    position
+                )
+
+                if (positionInContainer != null) {
+                    startSelection(
+                        position = positionInContainer,
+                        isStartHandle = false,
+                        adjustment = selectionMode
+                    )
+
+                    focusRequester.requestFocus()
+                    hideSelectionToolbar()
+                }
+            }
+
+        selectionRegistrar.onSelectionUpdateSelectAll =
+            { selectableId ->
+                val (newSelection, newSubselection) = selectAll(
+                    selectableId = selectableId,
+                    previousSelection = selection,
+                )
+                if (newSelection != selection) {
+                    selectionRegistrar.subselections = newSubselection
+                    onSelectionChange(newSelection)
+                }
+
+                focusRequester.requestFocus()
+                hideSelectionToolbar()
+            }
+
+        selectionRegistrar.onSelectionUpdateCallback =
+            { layoutCoordinates, newPosition, previousPosition, isStartHandle, selectionMode ->
+                val newPositionInContainer =
+                    convertToContainerCoordinates(layoutCoordinates, newPosition)
+                val previousPositionInContainer =
+                    convertToContainerCoordinates(layoutCoordinates, previousPosition)
+
+                updateSelection(
+                    newPosition = newPositionInContainer,
+                    previousPosition = previousPositionInContainer,
+                    isStartHandle = isStartHandle,
+                    adjustment = selectionMode
+                )
+            }
+
+        selectionRegistrar.onSelectionUpdateEndCallback = {
+            showSelectionToolbar()
+            // This property is set by updateSelection while dragging, so we need to clear it after
+            // the original selection drag.
+            draggingHandle = null
+            currentDragPosition = null
+        }
+
+        selectionRegistrar.onSelectableChangeCallback = { selectableKey ->
+            if (selectableKey in selectionRegistrar.subselections) {
+                // clear the selection range of each Selectable.
+                onRelease()
+                selection = null
+            }
+        }
+
+        selectionRegistrar.afterSelectableUnsubscribe = { selectableKey ->
+            if (
+                selectableKey == selection?.start?.selectableId ||
+                selectableKey == selection?.end?.selectableId
+            ) {
+                // The selectable that contains a selection handle just unsubscribed.
+                // Hide selection handles for now
+                startHandlePosition = null
+                endHandlePosition = null
+            }
+        }
+    }
+
+    /**
+     * Returns the [Selectable] responsible for managing the given [Selection.AnchorInfo], or null
+     * if the anchor is not from a currently-registered [Selectable].
+     */
+    internal fun getAnchorSelectable(anchor: Selection.AnchorInfo): Selectable? {
+        return selectionRegistrar.selectableMap[anchor.selectableId]
+    }
+
+    private fun updateHandleOffsets() {
+        val selection = selection
+        val containerCoordinates = containerLayoutCoordinates
+        val startSelectable = selection?.start?.let(::getAnchorSelectable)
+        val endSelectable = selection?.end?.let(::getAnchorSelectable)
+        val startLayoutCoordinates = startSelectable?.getLayoutCoordinates()
+        val endLayoutCoordinates = endSelectable?.getLayoutCoordinates()
+        if (
+            selection == null ||
+            containerCoordinates == null ||
+            !containerCoordinates.isAttached ||
+            startLayoutCoordinates == null ||
+            endLayoutCoordinates == null
+        ) {
+            this.startHandlePosition = null
+            this.endHandlePosition = null
+            return
+        }
+
+        val startHandlePosition = containerCoordinates.localPositionOf(
+            startLayoutCoordinates,
+            startSelectable.getHandlePosition(
+                selection = selection,
+                isStartHandle = true
+            )
+        )
+        val endHandlePosition = containerCoordinates.localPositionOf(
+            endLayoutCoordinates,
+            endSelectable.getHandlePosition(
+                selection = selection,
+                isStartHandle = false
+            )
+        )
+
+        val visibleBounds = containerCoordinates.visibleBounds()
+        this.startHandlePosition =
+            if (visibleBounds.containsInclusive(startHandlePosition)) startHandlePosition else null
+        this.endHandlePosition =
+            if (visibleBounds.containsInclusive(endHandlePosition)) endHandlePosition else null
+    }
+
+    /**
+     * Returns non-nullable [containerLayoutCoordinates].
+     */
+    internal fun requireContainerCoordinates(): LayoutCoordinates {
+        val coordinates = containerLayoutCoordinates
+        require(coordinates != null)
+        require(coordinates.isAttached)
+        return coordinates
+    }
+
+    internal fun selectAll(
+        selectableId: Long,
+        previousSelection: Selection?
+    ): Pair<Selection?, Map<Long, Selection>> {
+        val subselections = mutableMapOf<Long, Selection>()
+        val newSelection = selectionRegistrar.sort(requireContainerCoordinates())
+            .fastFold(null) { mergedSelection: Selection?, selectable: Selectable ->
+                val selection = if (selectable.selectableId == selectableId)
+                    selectable.getSelectAllSelection() else null
+                selection?.let { subselections[selectable.selectableId] = it }
+                merge(mergedSelection, selection)
+            }
+        if (newSelection != previousSelection) {
+            hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+        }
+        return Pair(newSelection, subselections)
+    }
+
+    internal fun getSelectedText(): AnnotatedString? {
+        val selectables = selectionRegistrar.sort(requireContainerCoordinates())
+        var selectedText: AnnotatedString? = null
+
+        selection?.let {
+            for (i in selectables.indices) {
+                val selectable = selectables[i]
+                // Continue if the current selectable is before the selection starts.
+                if (selectable.selectableId != it.start.selectableId &&
+                    selectable.selectableId != it.end.selectableId &&
+                    selectedText == null
+                ) continue
+
+                val currentSelectedText = getCurrentSelectedText(
+                    selectable = selectable,
+                    selection = it
+                )
+                selectedText = selectedText?.plus(currentSelectedText) ?: currentSelectedText
+
+                // Break if the current selectable is the last selected selectable.
+                if (selectable.selectableId == it.end.selectableId && !it.handlesCrossed ||
+                    selectable.selectableId == it.start.selectableId && it.handlesCrossed
+                ) break
+            }
+        }
+        return selectedText
+    }
+
+    internal fun copy() {
+        val selectedText = getSelectedText()
+        selectedText?.let { clipboardManager?.setText(it) }
+    }
+
+    /**
+     * This function get the selected region as a Rectangle region, and pass it to [TextToolbar]
+     * to make the FloatingToolbar show up in the proper place. In addition, this function passes
+     * the copy method as a callback when "copy" is clicked.
+     */
+    internal fun showSelectionToolbar() {
+        if (hasFocus) {
+            selection?.let {
+                textToolbar?.showMenu(
+                    getContentRect(),
+                    onCopyRequested = {
+                        copy()
+                        onRelease()
+                    }
+                )
+            }
+        }
+    }
+
+    internal fun hideSelectionToolbar() {
+        if (hasFocus && textToolbar?.status == TextToolbarStatus.Shown) {
+            textToolbar?.hide()
+        }
+    }
+
+    private fun updateSelectionToolbarPosition() {
+        if (hasFocus && textToolbar?.status == TextToolbarStatus.Shown) {
+            showSelectionToolbar()
+        }
+    }
+
+    /**
+     * Calculate selected region as [Rect]. The top is the top of the first selected
+     * line, and the bottom is the bottom of the last selected line. The left is the leftmost
+     * handle's horizontal coordinates, and the right is the rightmost handle's coordinates.
+     */
+    private fun getContentRect(): Rect {
+        val selection = selection ?: return Rect.Zero
+        val startSelectable = getAnchorSelectable(selection.start)
+        val endSelectable = getAnchorSelectable(selection.end)
+        val startLayoutCoordinates = startSelectable?.getLayoutCoordinates() ?: return Rect.Zero
+        val endLayoutCoordinates = endSelectable?.getLayoutCoordinates() ?: return Rect.Zero
+
+        val localLayoutCoordinates = containerLayoutCoordinates
+        if (localLayoutCoordinates != null && localLayoutCoordinates.isAttached) {
+            var startOffset = localLayoutCoordinates.localPositionOf(
+                startLayoutCoordinates,
+                startSelectable.getHandlePosition(
+                    selection = selection,
+                    isStartHandle = true
+                )
+            )
+            var endOffset = localLayoutCoordinates.localPositionOf(
+                endLayoutCoordinates,
+                endSelectable.getHandlePosition(
+                    selection = selection,
+                    isStartHandle = false
+                )
+            )
+
+            startOffset = localLayoutCoordinates.localToRoot(startOffset)
+            endOffset = localLayoutCoordinates.localToRoot(endOffset)
+
+            val left = min(startOffset.x, endOffset.x)
+            val right = max(startOffset.x, endOffset.x)
+
+            var startTop = localLayoutCoordinates.localPositionOf(
+                startLayoutCoordinates,
+                Offset(
+                    0f,
+                    startSelectable.getBoundingBox(selection.start.offset).top
+                )
+            )
+
+            var endTop = localLayoutCoordinates.localPositionOf(
+                endLayoutCoordinates,
+                Offset(
+                    0.0f,
+                    endSelectable.getBoundingBox(selection.end.offset).top
+                )
+            )
+
+            startTop = localLayoutCoordinates.localToRoot(startTop)
+            endTop = localLayoutCoordinates.localToRoot(endTop)
+
+            val top = min(startTop.y, endTop.y)
+            val bottom = max(startOffset.y, endOffset.y) + (HandleHeight.value * 4.0).toFloat()
+
+            return Rect(
+                left,
+                top,
+                right,
+                bottom
+            )
+        }
+        return Rect.Zero
+    }
+
+    // This is for PressGestureDetector to cancel the selection.
+    fun onRelease() {
+        selectionRegistrar.subselections = emptyMap()
+        hideSelectionToolbar()
+        if (selection != null) {
+            onSelectionChange(null)
+            hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+        }
+    }
+
+    fun handleDragObserver(isStartHandle: Boolean): TextDragObserver = object : TextDragObserver {
+        override fun onDown(point: Offset) {
+            val selection = selection ?: return
+            val anchor = if (isStartHandle) selection.start else selection.end
+            val selectable = getAnchorSelectable(anchor) ?: return
+            // The LayoutCoordinates of the composable where the drag gesture should begin. This
+            // is used to convert the position of the beginning of the drag gesture from the
+            // composable coordinates to selection container coordinates.
+            val beginLayoutCoordinates = selectable.getLayoutCoordinates() ?: return
+
+            // The position of the character where the drag gesture should begin. This is in
+            // the composable coordinates.
+            val beginCoordinates = getAdjustedCoordinates(
+                selectable.getHandlePosition(
+                    selection = selection, isStartHandle = isStartHandle
+                )
+            )
+
+            // Convert the position where drag gesture begins from composable coordinates to
+            // selection container coordinates.
+            currentDragPosition = requireContainerCoordinates().localPositionOf(
+                beginLayoutCoordinates,
+                beginCoordinates
+            )
+            draggingHandle = if (isStartHandle) Handle.SelectionStart else Handle.SelectionEnd
+        }
+
+        override fun onUp() {
+            draggingHandle = null
+            currentDragPosition = null
+        }
+
+        override fun onStart(startPoint: Offset) {
+            hideSelectionToolbar()
+            val selection = selection!!
+            val startSelectable =
+                selectionRegistrar.selectableMap[selection.start.selectableId]
+            val endSelectable =
+                selectionRegistrar.selectableMap[selection.end.selectableId]
+            // The LayoutCoordinates of the composable where the drag gesture should begin. This
+            // is used to convert the position of the beginning of the drag gesture from the
+            // composable coordinates to selection container coordinates.
+            val beginLayoutCoordinates = if (isStartHandle) {
+                startSelectable?.getLayoutCoordinates()!!
+            } else {
+                endSelectable?.getLayoutCoordinates()!!
+            }
+
+            // The position of the character where the drag gesture should begin. This is in
+            // the composable coordinates.
+            val beginCoordinates = getAdjustedCoordinates(
+                if (isStartHandle) {
+                    startSelectable!!.getHandlePosition(
+                        selection = selection, isStartHandle = true
+                    )
+                } else {
+                    endSelectable!!.getHandlePosition(
+                        selection = selection, isStartHandle = false
+                    )
+                }
+            )
+
+            // Convert the position where drag gesture begins from composable coordinates to
+            // selection container coordinates.
+            dragBeginPosition = requireContainerCoordinates().localPositionOf(
+                beginLayoutCoordinates,
+                beginCoordinates
+            )
+
+            // Zero out the total distance that being dragged.
+            dragTotalDistance = Offset.Zero
+        }
+
+        override fun onDrag(delta: Offset) {
+            dragTotalDistance += delta
+            val endPosition = dragBeginPosition + dragTotalDistance
+            val consumed = updateSelection(
+                newPosition = endPosition,
+                previousPosition = dragBeginPosition,
+                isStartHandle = isStartHandle,
+                adjustment = SelectionAdjustment.CharacterWithWordAccelerate
+            )
+            if (consumed) {
+                dragBeginPosition = endPosition
+                dragTotalDistance = Offset.Zero
+            }
+        }
+
+        override fun onStop() {
+            showSelectionToolbar()
+            draggingHandle = null
+            currentDragPosition = null
+        }
+
+        override fun onCancel() {
+            showSelectionToolbar()
+            draggingHandle = null
+            currentDragPosition = null
+        }
+    }
+
+    /**
+     * Detect tap without consuming the up event.
+     */
+    private suspend fun PointerInputScope.detectNonConsumingTap(onTap: (Offset) -> Unit) {
+        awaitEachGesture {
+            waitForUpOrCancellation()?.let {
+                onTap(it.position)
+            }
+        }
+    }
+
+    private fun Modifier.onClearSelectionRequested(block: () -> Unit): Modifier {
+        return if (hasFocus) pointerInput(Unit) { detectNonConsumingTap { block() } } else this
+    }
+
+    private fun convertToContainerCoordinates(
+        layoutCoordinates: LayoutCoordinates,
+        offset: Offset
+    ): Offset? {
+        val coordinates = containerLayoutCoordinates
+        if (coordinates == null || !coordinates.isAttached) return null
+        return requireContainerCoordinates().localPositionOf(layoutCoordinates, offset)
+    }
+
+    /**
+     * Cancel the previous selection and start a new selection at the given [position].
+     * It's used for long-press, double-click, triple-click and so on to start selection.
+     *
+     * @param position initial position of the selection. Both start and end handle is considered
+     * at this position.
+     * @param isStartHandle whether it's considered as the start handle moving. This parameter
+     * will influence the [SelectionAdjustment]'s behavior. For example,
+     * [SelectionAdjustment.Character] only adjust the moving handle.
+     * @param adjustment the selection adjustment.
+     */
+    private fun startSelection(
+        position: Offset,
+        isStartHandle: Boolean,
+        adjustment: SelectionAdjustment
+    ) {
+        updateSelection(
+            startHandlePosition = position,
+            endHandlePosition = position,
+            previousHandlePosition = null,
+            isStartHandle = isStartHandle,
+            adjustment = adjustment
+        )
+    }
+
+    /**
+     * Updates the selection after one of the selection handle moved.
+     *
+     * @param newPosition the new position of the moving selection handle.
+     * @param previousPosition the previous position of the moving selection handle.
+     * @param isStartHandle whether the moving selection handle is the start handle.
+     * @param adjustment the [SelectionAdjustment] used to adjust the raw selection range and
+     * produce the final selection range.
+     *
+     * @return a boolean representing whether the movement is consumed.
+     *
+     * @see SelectionAdjustment
+     */
+    internal fun updateSelection(
+        newPosition: Offset?,
+        previousPosition: Offset?,
+        isStartHandle: Boolean,
+        adjustment: SelectionAdjustment,
+    ): Boolean {
+        if (newPosition == null) return false
+        val otherHandlePosition = selection?.let { selection ->
+            val otherSelectableId = if (isStartHandle) {
+                selection.end.selectableId
+            } else {
+                selection.start.selectableId
+            }
+            val otherSelectable =
+                selectionRegistrar.selectableMap[otherSelectableId] ?: return@let null
+            convertToContainerCoordinates(
+                otherSelectable.getLayoutCoordinates()!!,
+                getAdjustedCoordinates(
+                    otherSelectable.getHandlePosition(selection, !isStartHandle)
+                )
+            )
+        } ?: return false
+
+        val startHandlePosition = if (isStartHandle) newPosition else otherHandlePosition
+        val endHandlePosition = if (isStartHandle) otherHandlePosition else newPosition
+
+        return updateSelection(
+            startHandlePosition = startHandlePosition,
+            endHandlePosition = endHandlePosition,
+            previousHandlePosition = previousPosition,
+            isStartHandle = isStartHandle,
+            adjustment = adjustment
+        )
+    }
+
+    /**
+     * Updates the selection after one of the selection handle moved.
+     *
+     * To make sure that [SelectionAdjustment] works correctly, it's expected that only one
+     * selection handle is updated each time. The only exception is that when a new selection is
+     * started. In this case, [previousHandlePosition] is always null.
+     *
+     * @param startHandlePosition the position of the start selection handle.
+     * @param endHandlePosition the position of the end selection handle.
+     * @param previousHandlePosition the position of the moving handle before the update.
+     * @param isStartHandle whether the moving selection handle is the start handle.
+     * @param adjustment the [SelectionAdjustment] used to adjust the raw selection range and
+     * produce the final selection range.
+     *
+     * @return a boolean representing whether the movement is consumed. It's useful for the case
+     * where a selection handle is updating consecutively. When the return value is true, it's
+     * expected that the caller will update the [startHandlePosition] to be the given
+     * [endHandlePosition] in following calls.
+     *
+     * @see SelectionAdjustment
+     */
+    internal fun updateSelection(
+        startHandlePosition: Offset,
+        endHandlePosition: Offset,
+        previousHandlePosition: Offset?,
+        isStartHandle: Boolean,
+        adjustment: SelectionAdjustment,
+    ): Boolean {
+        draggingHandle = if (isStartHandle) Handle.SelectionStart else Handle.SelectionEnd
+        currentDragPosition = if (isStartHandle) startHandlePosition else endHandlePosition
+        val newSubselections = mutableMapOf<Long, Selection>()
+        var moveConsumed = false
+        val newSelection = selectionRegistrar.sort(requireContainerCoordinates())
+            .fastFold(null) { mergedSelection: Selection?, selectable: Selectable ->
+                val previousSubselection =
+                    selectionRegistrar.subselections[selectable.selectableId]
+                val (selection, consumed) = selectable.updateSelection(
+                    startHandlePosition = startHandlePosition,
+                    endHandlePosition = endHandlePosition,
+                    previousHandlePosition = previousHandlePosition,
+                    isStartHandle = isStartHandle,
+                    containerLayoutCoordinates = requireContainerCoordinates(),
+                    adjustment = adjustment,
+                    previousSelection = previousSubselection,
+                )
+
+                moveConsumed = moveConsumed || consumed
+                selection?.let { newSubselections[selectable.selectableId] = it }
+                merge(mergedSelection, selection)
+            }
+        if (newSelection != selection) {
+            hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+            selectionRegistrar.subselections = newSubselections
+            onSelectionChange(newSelection)
+        }
+        return moveConsumed
+    }
+
+    fun contextMenuOpenAdjustment(position: Offset) {
+        val isEmptySelection = selection?.toTextRange()?.collapsed ?: true
+        // TODO(b/209483184) the logic should be more complex here, it should check that current
+        // selection doesn't include click position
+        if (isEmptySelection) {
+            startSelection(
+                position = position,
+                isStartHandle = true,
+                adjustment = SelectionAdjustment.Word
+            )
+        }
+    }
+}
+
+internal fun merge(lhs: Selection?, rhs: Selection?): Selection? {
+    return lhs?.merge(rhs) ?: rhs
+}
+
+internal expect fun isCopyKeyEvent(keyEvent: KeyEvent): Boolean
+
+internal expect fun Modifier.selectionMagnifier(manager: SelectionManager): Modifier
+
+internal fun calculateSelectionMagnifierCenterAndroid(
+    manager: SelectionManager,
+    magnifierSize: IntSize
+): Offset {
+    fun getMagnifierCenter(anchor: Selection.AnchorInfo, isStartHandle: Boolean): Offset {
+        val selectable = manager.getAnchorSelectable(anchor) ?: return Offset.Unspecified
+        val containerCoordinates = manager.containerLayoutCoordinates ?: return Offset.Unspecified
+        val selectableCoordinates = selectable.getLayoutCoordinates() ?: return Offset.Unspecified
+        // The end offset is exclusive.
+        val offset = if (isStartHandle) anchor.offset else anchor.offset - 1
+
+        // The horizontal position doesn't snap to cursor positions but should directly track the
+        // actual drag.
+        val localDragPosition = selectableCoordinates.localPositionOf(
+            containerCoordinates,
+            manager.currentDragPosition!!
+        )
+        val dragX = localDragPosition.x
+        // But it is constrained by the horizontal bounds of the current line.
+        val centerX = selectable.getRangeOfLineContaining(offset).let { line ->
+            val lineMin = selectable.getBoundingBox(line.min)
+            // line.end is exclusive, but we want the bounding box of the actual last character in
+            // the line.
+            val lineMax = selectable.getBoundingBox((line.max - 1).coerceAtLeast(line.min))
+            val minX = minOf(lineMin.left, lineMax.left)
+            val maxX = maxOf(lineMin.right, lineMax.right)
+            dragX.coerceIn(minX, maxX)
+        }
+
+        // Hide the magnifier when dragged too far (outside the horizontal bounds of how big the
+        // magnifier actually is). See
+        // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/widget/Editor.java;l=5228-5231;drc=2fdb6bd709be078b72f011334362456bb758922c
+        if ((dragX - centerX).absoluteValue > magnifierSize.width / 2) {
+            return Offset.Unspecified
+        }
+
+        // Let the selectable determine the vertical position of the magnifier, since it should be
+        // clamped to the center of text lines.
+        val anchorBounds = selectable.getBoundingBox(offset)
+        val centerY = anchorBounds.center.y
+
+        return containerCoordinates.localPositionOf(
+            sourceCoordinates = selectableCoordinates,
+            relativeToSource = Offset(centerX, centerY)
+        )
+    }
+
+    val selection = manager.selection ?: return Offset.Unspecified
+    return when (manager.draggingHandle) {
+        null -> return Offset.Unspecified
+        Handle.SelectionStart -> getMagnifierCenter(selection.start, isStartHandle = true)
+        Handle.SelectionEnd -> getMagnifierCenter(selection.end, isStartHandle = false)
+        Handle.Cursor -> error("SelectionContainer does not support cursor")
+    }
+}
+
+internal fun getCurrentSelectedText(
+    selectable: Selectable,
+    selection: Selection
+): AnnotatedString {
+    val currentText = selectable.getText()
+
+    return if (
+        selectable.selectableId != selection.start.selectableId &&
+        selectable.selectableId != selection.end.selectableId
+    ) {
+        // Select the full text content if the current selectable is between the
+        // start and the end selectables.
+        currentText
+    } else if (
+        selectable.selectableId == selection.start.selectableId &&
+        selectable.selectableId == selection.end.selectableId
+    ) {
+        // Select partial text content if the current selectable is the start and
+        // the end selectable.
+        if (selection.handlesCrossed) {
+            currentText.subSequence(selection.end.offset, selection.start.offset)
+        } else {
+            currentText.subSequence(selection.start.offset, selection.end.offset)
+        }
+    } else if (selectable.selectableId == selection.start.selectableId) {
+        // Select partial text content if the current selectable is the start
+        // selectable.
+        if (selection.handlesCrossed) {
+            currentText.subSequence(0, selection.start.offset)
+        } else {
+            currentText.subSequence(selection.start.offset, currentText.length)
+        }
+    } else {
+        // Selectable partial text content if the current selectable is the end
+        // selectable.
+        if (selection.handlesCrossed) {
+            currentText.subSequence(selection.end.offset, currentText.length)
+        } else {
+            currentText.subSequence(0, selection.end.offset)
+        }
+    }
+}
+
+/** Returns the boundary of the visible area in this [LayoutCoordinates]. */
+internal fun LayoutCoordinates.visibleBounds(): Rect {
+    // globalBounds is the global boundaries of this LayoutCoordinates after it's clipped by
+    // parents. We can think it as the global visible bounds of this Layout. Here globalBounds
+    // is convert to local, which is the boundary of the visible area within the LayoutCoordinates.
+    val boundsInWindow = boundsInWindow()
+    return Rect(
+        windowToLocal(boundsInWindow.topLeft),
+        windowToLocal(boundsInWindow.bottomRight)
+    )
+}
+
+internal fun Rect.containsInclusive(offset: Offset): Boolean =
+    offset.x in left..right && offset.y in top..bottom
+
+internal enum class Handle {
+    Cursor,
+    SelectionStart,
+    SelectionEnd
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionMode.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionMode.kt
new file mode 100644
index 0000000..da7e8e0
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionMode.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.newtext.text.copypasta.selection
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+
+/**
+ * The enum class allows user to decide the selection mode.
+ */
+internal enum class SelectionMode {
+    /**
+     * When selection handles are dragged across composables, selection extends by row, for example,
+     * when the end selection handle is dragged down, upper rows will be selected first, and the
+     * lower rows.
+     */
+    Vertical {
+        override fun compare(position: Offset, bounds: Rect): Int {
+            if (bounds.contains(position)) return 0
+
+            // When the position of the selection handle is on the top of the composable, and the
+            // not on the right of the composable, it's considered as start.
+            if (position.y < bounds.top) return -1
+
+            // When the position of the selection handle is on the left of the composable, and not
+            // below the bottom of composable, it's considered as start.
+            if (position.x < bounds.left && position.y < bounds.bottom) return -1
+
+            // In all other cases, the selection handle is considered as the end.
+            return 1
+        }
+    },
+
+    /**
+     * When selection handles are dragged across composables, selection extends by column, for example,
+     * when the end selection handle is dragged to the right, left columns will be selected first,
+     * and the right rows.
+     */
+    Horizontal {
+        override fun compare(position: Offset, bounds: Rect): Int {
+            if (bounds.contains(position)) return 0
+
+            // When the end of the selection is on the left of the composable, the composable is
+            // outside of the selection range.
+            if (position.x < bounds.left) return -1
+
+            // When the end of the selection is on the top of the composable, and the not on the
+            // right of the composable, the composable is outside of the selection range.
+            if (position.y < bounds.top && position.x < bounds.right) return -1
+
+            // In all other cases, the selection handle is considered as the end.
+            return 1
+        }
+    };
+
+    /**
+     * A compare a selection handle with a  [Selectable] boundary. This defines whether an out of
+     * boundary selection handle is treated as the start or the end of the Selectable. If the
+     * [Selectable] is a text selectable, then the start is the index 0, and end corresponds to
+     * the text length.
+     *
+     * @param position the position of the selection handle.
+     * @param bounds the boundary of the [Selectable].
+     * @return 0 if the selection handle [position] is within the [bounds]; a negative value if
+     * the selection handle is considered as "start" of the [Selectable]; a positive value if the
+     * selection handle is considered as the "end" of the [Selectable].
+     */
+    internal abstract fun compare(position: Offset, bounds: Rect): Int
+
+    /**
+     * Decides if Composable which has [bounds], should be accepted by the selection and
+     * change its selected state for a selection that starts at [start] and ends at [end].
+     *
+     * @param bounds Composable bounds of the widget to be checked.
+     * @param start The start coordinates of the selection, in SelectionContainer range.
+     * @param end The end coordinates of the selection, in SelectionContainer range.
+     */
+    internal fun isSelected(
+        bounds: Rect,
+        start: Offset,
+        end: Offset
+    ): Boolean {
+        // If either of the start or end is contained by bounds, the composable is selected.
+        if (bounds.contains(start) || bounds.contains(end)) {
+            return true
+        }
+        // Compare the location of start and end to the bound. If both are on the same side, return
+        // false, otherwise return true.
+        val compareStart = compare(start, bounds)
+        val compareEnd = compare(end, bounds)
+        return (compareStart > 0) xor (compareEnd > 0)
+    }
+}
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionRegistrar.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionRegistrar.kt
new file mode 100644
index 0000000..dd1f86c
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionRegistrar.kt
@@ -0,0 +1,159 @@
+/*
+ * 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.compose.foundation.newtext.text.copypasta.selection
+
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.layout.LayoutCoordinates
+
+/**
+ *  An interface allowing a composable to subscribe and unsubscribe to selection changes.
+ */
+internal interface SelectionRegistrar {
+    /**
+     * The map stored current selection information on each [Selectable]. A selectable can query
+     * its selected range using its [Selectable.selectableId]. This field is backed by a
+     * [MutableState]. And any composable reading this field will be recomposed once its value
+     * changed.
+     */
+    val subselections: Map<Long, Selection>
+
+    /**
+     * Subscribe to SelectionContainer selection changes.
+     * @param selectable the [Selectable] that is subscribing to this [SelectionRegistrar].
+     */
+    fun subscribe(selectable: Selectable): Selectable
+
+    /**
+     * Unsubscribe from SelectionContainer selection changes.
+     * @param selectable the [Selectable] that is unsubscribing to this [SelectionRegistrar].
+     */
+    fun unsubscribe(selectable: Selectable)
+
+    /**
+     * Return a unique ID for a [Selectable].
+     * @see [Selectable.selectableId]
+     */
+    fun nextSelectableId(): Long
+
+    /**
+     * When the Global Position of a subscribed [Selectable] changes, this method
+     * is called.
+     */
+    fun notifyPositionChange(selectableId: Long)
+
+    /**
+     * Call this method to notify the [SelectionContainer] that the selection has been initiated.
+     * Depends on the input, [notifySelectionUpdate] may be called repeatedly after
+     * [notifySelectionUpdateStart] is called. And [notifySelectionUpdateEnd] should always be
+     * called after selection finished.
+     * For example:
+     *  1. User long pressed the text and then release. [notifySelectionUpdateStart] should be
+     *  called followed by [notifySelectionUpdateEnd] being called once.
+     *  2. User long pressed the text and then drag a distance and then release.
+     *  [notifySelectionUpdateStart] should be called first after the user long press, and then
+     *  [notifySelectionUpdate] is called several times reporting the updates, in the end
+     *  [notifySelectionUpdateEnd] is called to finish the selection.
+     *
+     * @param layoutCoordinates [LayoutCoordinates] of the [Selectable].
+     * @param startPosition coordinates of where the selection is initiated.
+     * @param adjustment selection should be adjusted according to this param
+     *
+     * @see notifySelectionUpdate
+     * @see notifySelectionUpdateEnd
+     */
+    fun notifySelectionUpdateStart(
+        layoutCoordinates: LayoutCoordinates,
+        startPosition: Offset,
+        adjustment: SelectionAdjustment
+    )
+
+    /**
+     * Call this method to notify the [SelectionContainer] that the selection has been initiated
+     * with selectAll [Selection].
+     *
+     * @param selectableId [selectableId] of the [Selectable]
+     */
+    fun notifySelectionUpdateSelectAll(selectableId: Long)
+
+    /**
+     * Call this method to notify the [SelectionContainer] that one of the selection handle has
+     * moved and selection should be updated.
+     * The caller of this method should make sure that [notifySelectionUpdateStart] is always
+     * called once before calling this function. And [notifySelectionUpdateEnd] is always called
+     * once after the all updates finished.
+     *
+     * @param layoutCoordinates [LayoutCoordinates] of the [Selectable].
+     * @param previousPosition coordinates of where the selection starts.
+     * @param newPosition coordinates of where the selection ends.
+     * @param isStartHandle whether the moving selection handle the start handle.
+     * @param adjustment selection should be adjusted according to this parameter
+     *
+     * @return true if the selection handle movement is consumed. This function acts like a
+     * pointer input consumer when a selection handle is dragged. It expects the caller to
+     * accumulate the unconsumed pointer movement:
+     * 1. if it returns true, the caller will zero out the previous movement.
+     * 2. if it returns false, the caller will continue accumulate pointer movement.
+     * @see notifySelectionUpdateStart
+     * @see notifySelectionUpdateEnd
+     */
+    fun notifySelectionUpdate(
+        layoutCoordinates: LayoutCoordinates,
+        newPosition: Offset,
+        previousPosition: Offset,
+        isStartHandle: Boolean,
+        adjustment: SelectionAdjustment
+    ): Boolean
+
+    /**
+     * Call this method to notify the [SelectionContainer] that the selection update has stopped.
+     *
+     * @see notifySelectionUpdateStart
+     * @see notifySelectionUpdate
+     */
+    fun notifySelectionUpdateEnd()
+
+    /**
+     * Call this method to notify the [SelectionContainer] that the content of the passed
+     * selectable has been changed.
+     *
+     * @param selectableId the ID of the selectable whose the content has been updated.
+     */
+    fun notifySelectableChange(selectableId: Long)
+
+    companion object {
+        /**
+         * Representing an invalid ID for [Selectable].
+         */
+        const val InvalidSelectableId = 0L
+    }
+}
+
+/**
+ * Helper function that checks if there is a selection on this CoreText.
+ */
+internal fun SelectionRegistrar?.hasSelection(selectableId: Long): Boolean {
+    return this?.subselections?.containsKey(selectableId) ?: false
+}
+
+/**
+ * SelectionRegistrar CompositionLocal. Composables that implement selection logic can use this
+ * CompositionLocal to get a [SelectionRegistrar] in order to subscribe and unsubscribe to
+ * [SelectionRegistrar].
+ */
+internal val LocalSelectionRegistrar = compositionLocalOf<SelectionRegistrar?> { null }
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionRegistrarImpl.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionRegistrarImpl.kt
new file mode 100644
index 0000000..f08c238
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionRegistrarImpl.kt
@@ -0,0 +1,205 @@
+/*
+ * 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.compose.foundation.newtext.text.copypasta.selection
+
+import androidx.compose.foundation.newtext.text.copypasta.AtomicLong
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.layout.LayoutCoordinates
+
+internal class SelectionRegistrarImpl : SelectionRegistrar {
+    /**
+     * A flag to check if the [Selectable]s have already been sorted.
+     */
+    internal var sorted: Boolean = false
+
+    /**
+     * This is essentially the list of registered components that want
+     * to handle text selection that are below the SelectionContainer.
+     */
+    private val _selectables = mutableListOf<Selectable>()
+
+    /**
+     * Getter for handlers that returns a List.
+     */
+    internal val selectables: List<Selectable>
+        get() = _selectables
+
+    private val _selectableMap = mutableMapOf<Long, Selectable>()
+
+    /**
+     * A map from selectable keys to subscribed selectables.
+     */
+    internal val selectableMap: Map<Long, Selectable>
+        get() = _selectableMap
+
+    /**
+     * The incremental id to be assigned to each selectable. It starts from 1 and 0 is used to
+     * denote an invalid id.
+     * @see SelectionRegistrar.InvalidSelectableId
+     */
+    private var incrementId = AtomicLong(1)
+
+    /**
+     * The callback to be invoked when the position change was triggered.
+     */
+    internal var onPositionChangeCallback: ((Long) -> Unit)? = null
+
+    /**
+     * The callback to be invoked when the selection is initiated.
+     */
+    internal var onSelectionUpdateStartCallback:
+        ((LayoutCoordinates, Offset, SelectionAdjustment) -> Unit)? = null
+
+    /**
+     * The callback to be invoked when the selection is initiated with selectAll [Selection].
+     */
+    internal var onSelectionUpdateSelectAll: (
+        (Long) -> Unit
+    )? = null
+
+    /**
+     * The callback to be invoked when the selection is updated.
+     * If the first offset is null it means that the start of selection is unknown for the caller.
+     */
+    internal var onSelectionUpdateCallback:
+        ((LayoutCoordinates, Offset, Offset, Boolean, SelectionAdjustment) -> Boolean)? = null
+
+    /**
+     * The callback to be invoked when selection update finished.
+     */
+    internal var onSelectionUpdateEndCallback: (() -> Unit)? = null
+
+    /**
+     * The callback to be invoked when one of the selectable has changed.
+     */
+    internal var onSelectableChangeCallback: ((Long) -> Unit)? = null
+
+    /**
+     * The callback to be invoked after a selectable is unsubscribed from this [SelectionRegistrar].
+     */
+    internal var afterSelectableUnsubscribe: ((Long) -> Unit)? = null
+
+    override var subselections: Map<Long, Selection> by mutableStateOf(emptyMap())
+
+    override fun subscribe(selectable: Selectable): Selectable {
+        require(selectable.selectableId != SelectionRegistrar.InvalidSelectableId) {
+            "The selectable contains an invalid id: ${selectable.selectableId}"
+        }
+        require(!_selectableMap.containsKey(selectable.selectableId)) {
+            "Another selectable with the id: $selectable.selectableId has already subscribed."
+        }
+        _selectableMap[selectable.selectableId] = selectable
+        _selectables.add(selectable)
+        sorted = false
+        return selectable
+    }
+
+    override fun unsubscribe(selectable: Selectable) {
+        if (!_selectableMap.containsKey(selectable.selectableId)) return
+        _selectables.remove(selectable)
+        _selectableMap.remove(selectable.selectableId)
+        afterSelectableUnsubscribe?.invoke(selectable.selectableId)
+    }
+
+    override fun nextSelectableId(): Long {
+        var id = incrementId.getAndIncrement()
+        while (id == SelectionRegistrar.InvalidSelectableId) {
+            id = incrementId.getAndIncrement()
+        }
+        return id
+    }
+
+    /**
+     * Sort the list of registered [Selectable]s in [SelectionRegistrar]. Currently the order of
+     * selectables is geometric-based.
+     */
+    fun sort(containerLayoutCoordinates: LayoutCoordinates): List<Selectable> {
+        if (!sorted) {
+            // Sort selectables by y-coordinate first, and then x-coordinate, to match English
+            // hand-writing habit.
+            _selectables.sortWith { a: Selectable, b: Selectable ->
+                val layoutCoordinatesA = a.getLayoutCoordinates()
+                val layoutCoordinatesB = b.getLayoutCoordinates()
+
+                val positionA = if (layoutCoordinatesA != null) {
+                    containerLayoutCoordinates.localPositionOf(layoutCoordinatesA, Offset.Zero)
+                } else {
+                    Offset.Zero
+                }
+                val positionB = if (layoutCoordinatesB != null) {
+                    containerLayoutCoordinates.localPositionOf(layoutCoordinatesB, Offset.Zero)
+                } else {
+                    Offset.Zero
+                }
+
+                if (positionA.y == positionB.y) {
+                    compareValues(positionA.x, positionB.x)
+                } else {
+                    compareValues(positionA.y, positionB.y)
+                }
+            }
+            sorted = true
+        }
+        return selectables
+    }
+
+    override fun notifyPositionChange(selectableId: Long) {
+        // Set the variable sorted to be false, when the global position of a registered
+        // selectable changes.
+        sorted = false
+        onPositionChangeCallback?.invoke(selectableId)
+    }
+
+    override fun notifySelectionUpdateStart(
+        layoutCoordinates: LayoutCoordinates,
+        startPosition: Offset,
+        adjustment: SelectionAdjustment
+    ) {
+        onSelectionUpdateStartCallback?.invoke(layoutCoordinates, startPosition, adjustment)
+    }
+
+    override fun notifySelectionUpdateSelectAll(selectableId: Long) {
+        onSelectionUpdateSelectAll?.invoke(selectableId)
+    }
+
+    override fun notifySelectionUpdate(
+        layoutCoordinates: LayoutCoordinates,
+        newPosition: Offset,
+        previousPosition: Offset,
+        isStartHandle: Boolean,
+        adjustment: SelectionAdjustment
+    ): Boolean {
+        return onSelectionUpdateCallback?.invoke(
+            layoutCoordinates,
+            newPosition,
+            previousPosition,
+            isStartHandle,
+            adjustment
+        ) ?: true
+    }
+
+    override fun notifySelectionUpdateEnd() {
+        onSelectionUpdateEndCallback?.invoke()
+    }
+
+    override fun notifySelectableChange(selectableId: Long) {
+        onSelectableChangeCallback?.invoke(selectableId)
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SimpleLayout.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SimpleLayout.kt
new file mode 100644
index 0000000..9929a2e
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SimpleLayout.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.newtext.text.copypasta.selection
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastMap
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+import kotlin.math.max
+
+/**
+ * Selection is transparent in terms of measurement and layout and passes the same constraints to
+ * the children.
+ */
+@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))
+        }
+
+        layout(width, height) {
+            placeables.fastForEach { placeable ->
+                placeable.place(0, 0)
+            }
+        }
+    }
+}
+
+// copypasta from foundation to compile this copypasta
+@Suppress("BanInlineOptIn") // Treat Kotlin Contracts as non-experimental.
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T, R> List<T>.fastFold(initial: R, operation: (acc: R, T) -> R): R {
+    contract { callsInPlace(operation) }
+    var accumulator = initial
+    fastForEach { e ->
+        accumulator = operation(accumulator, e)
+    }
+    return accumulator
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/TextPreparedSelection.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/TextPreparedSelection.kt
new file mode 100644
index 0000000..a3e5817
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/TextPreparedSelection.kt
@@ -0,0 +1,432 @@
+/*
+ * 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.compose.foundation.newtext.text.copypasta.selection
+
+import androidx.compose.foundation.newtext.text.copypasta.TextLayoutResultProxy
+import androidx.compose.foundation.newtext.text.copypasta.findFollowingBreak
+import androidx.compose.foundation.newtext.text.copypasta.findParagraphEnd
+import androidx.compose.foundation.newtext.text.copypasta.findParagraphStart
+import androidx.compose.foundation.newtext.text.copypasta.findPrecedingBreak
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.CommitTextCommand
+import androidx.compose.ui.text.input.EditCommand
+import androidx.compose.ui.text.input.OffsetMapping
+import androidx.compose.ui.text.input.SetSelectionCommand
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.text.style.ResolvedTextDirection
+
+internal class TextPreparedSelectionState {
+    // it's set at the start of vertical navigation and used as the preferred value to set a new
+    // cursor position.
+    var cachedX: Float? = null
+
+    fun resetCachedX() {
+        cachedX = null
+    }
+}
+
+/**
+ * This utility class implements many selection-related operations on text (including basic
+ * cursor movements and deletions) and combines them, taking into account how the text was
+ * rendered. So, for example, [moveCursorToLineEnd] moves it to the visual line end.
+ *
+ * For many of these operations, it's particularly important to keep the difference between
+ * selection start and selection end. In some systems, they are called "anchor" and "caret"
+ * respectively. For example, for selection from scratch, after [moveCursorLeftByWord]
+ * [moveCursorRight] will move the left side of the selection, but after [moveCursorRightByWord]
+ * the right one.
+ *
+ * To use it in scope of text fields see [TextFieldPreparedSelection]
+ */
+internal abstract class BaseTextPreparedSelection<T : BaseTextPreparedSelection<T>>(
+    val originalText: AnnotatedString,
+    val originalSelection: TextRange,
+    val layoutResult: TextLayoutResult?,
+    val offsetMapping: OffsetMapping,
+    val state: TextPreparedSelectionState
+) {
+    var selection = originalSelection
+
+    var annotatedString = originalText
+    internal val text
+        get() = annotatedString.text
+
+    @Suppress("UNCHECKED_CAST")
+    protected inline fun <U> U.apply(resetCachedX: Boolean = true, block: U.() -> Unit): T {
+        if (resetCachedX) {
+            state.resetCachedX()
+        }
+        if (text.isNotEmpty()) {
+            block()
+        }
+        return this as T
+    }
+
+    protected fun setCursor(offset: Int) {
+        setSelection(offset, offset)
+    }
+
+    protected fun setSelection(start: Int, end: Int) {
+        selection = TextRange(start, end)
+    }
+
+    fun selectAll() = apply {
+        setSelection(0, text.length)
+    }
+
+    fun deselect() = apply {
+        setCursor(selection.end)
+    }
+
+    fun moveCursorLeft() = apply {
+        if (isLtr()) {
+            moveCursorPrev()
+        } else {
+            moveCursorNext()
+        }
+    }
+
+    fun moveCursorRight() = apply {
+        if (isLtr()) {
+            moveCursorNext()
+        } else {
+            moveCursorPrev()
+        }
+    }
+
+    /**
+     * If there is already a selection, collapse it to the left side. Otherwise, execute [or]
+     */
+    fun collapseLeftOr(or: T.() -> Unit) = apply {
+        if (selection.collapsed) {
+            @Suppress("UNCHECKED_CAST")
+            or(this as T)
+        } else {
+            if (isLtr()) {
+                setCursor(selection.min)
+            } else {
+                setCursor(selection.max)
+            }
+        }
+    }
+
+    /**
+     * If there is already a selection, collapse it to the right side. Otherwise, execute [or]
+     */
+    fun collapseRightOr(or: T.() -> Unit) = apply {
+        if (selection.collapsed) {
+            @Suppress("UNCHECKED_CAST")
+            or(this as T)
+        } else {
+            if (isLtr()) {
+                setCursor(selection.max)
+            } else {
+                setCursor(selection.min)
+            }
+        }
+    }
+
+    /**
+     * Returns the index of the character break preceding the end of [selection].
+     */
+    fun getPrecedingCharacterIndex() = annotatedString.text.findPrecedingBreak(selection.end)
+
+    /**
+     * Returns the index of the character break following the end of [selection]. Returns
+     * [NoCharacterFound] if there are no more breaks before the end of the string.
+     */
+    fun getNextCharacterIndex() = annotatedString.text.findFollowingBreak(selection.end)
+
+    private fun moveCursorPrev() = apply {
+        val prev = getPrecedingCharacterIndex()
+        if (prev != -1) setCursor(prev)
+    }
+
+    private fun moveCursorNext() = apply {
+        val next = getNextCharacterIndex()
+        if (next != -1) setCursor(next)
+    }
+
+    fun moveCursorToHome() = apply {
+        setCursor(0)
+    }
+
+    fun moveCursorToEnd() = apply {
+        setCursor(text.length)
+    }
+
+    fun moveCursorLeftByWord() = apply {
+        if (isLtr()) {
+            moveCursorPrevByWord()
+        } else {
+            moveCursorNextByWord()
+        }
+    }
+
+    fun moveCursorRightByWord() = apply {
+        if (isLtr()) {
+            moveCursorNextByWord()
+        } else {
+            moveCursorPrevByWord()
+        }
+    }
+
+    fun getNextWordOffset(): Int? = layoutResult?.getNextWordOffsetForLayout()
+
+    private fun moveCursorNextByWord() = apply {
+        getNextWordOffset()?.let { setCursor(it) }
+    }
+
+    fun getPreviousWordOffset(): Int? = layoutResult?.getPrevWordOffset()
+
+    private fun moveCursorPrevByWord() = apply {
+        getPreviousWordOffset()?.let { setCursor(it) }
+    }
+
+    fun moveCursorPrevByParagraph() = apply {
+        setCursor(getParagraphStart())
+    }
+
+    fun moveCursorNextByParagraph() = apply {
+        setCursor(getParagraphEnd())
+    }
+
+    fun moveCursorUpByLine() = apply(false) {
+        layoutResult?.jumpByLinesOffset(-1)?.let { setCursor(it) }
+    }
+
+    fun moveCursorDownByLine() = apply(false) {
+        layoutResult?.jumpByLinesOffset(1)?.let { setCursor(it) }
+    }
+
+    fun getLineStartByOffset(): Int? = layoutResult?.getLineStartByOffsetForLayout()
+
+    fun moveCursorToLineStart() = apply {
+        getLineStartByOffset()?.let { setCursor(it) }
+    }
+
+    fun getLineEndByOffset(): Int? = layoutResult?.getLineEndByOffsetForLayout()
+
+    fun moveCursorToLineEnd() = apply {
+        getLineEndByOffset()?.let { setCursor(it) }
+    }
+
+    fun moveCursorToLineLeftSide() = apply {
+        if (isLtr()) {
+            moveCursorToLineStart()
+        } else {
+            moveCursorToLineEnd()
+        }
+    }
+
+    fun moveCursorToLineRightSide() = apply {
+        if (isLtr()) {
+            moveCursorToLineEnd()
+        } else {
+            moveCursorToLineStart()
+        }
+    }
+
+    // it selects a text from the original selection start to a current selection end
+    fun selectMovement() = apply(false) {
+        selection = TextRange(originalSelection.start, selection.end)
+    }
+
+    private fun isLtr(): Boolean {
+        val direction = layoutResult?.getParagraphDirection(transformedEndOffset())
+        return direction != ResolvedTextDirection.Rtl
+    }
+
+    private fun TextLayoutResult.getNextWordOffsetForLayout(
+        currentOffset: Int = transformedEndOffset()
+    ): Int {
+        if (currentOffset >= originalText.length) {
+            return originalText.length
+        }
+        val currentWord = getWordBoundary(charOffset(currentOffset))
+        return if (currentWord.end <= currentOffset) {
+            getNextWordOffsetForLayout(currentOffset + 1)
+        } else {
+            offsetMapping.transformedToOriginal(currentWord.end)
+        }
+    }
+
+    private fun TextLayoutResult.getPrevWordOffset(
+        currentOffset: Int = transformedEndOffset()
+    ): Int {
+        if (currentOffset < 0) {
+            return 0
+        }
+        val currentWord = getWordBoundary(charOffset(currentOffset))
+        return if (currentWord.start >= currentOffset) {
+            getPrevWordOffset(currentOffset - 1)
+        } else {
+            offsetMapping.transformedToOriginal(currentWord.start)
+        }
+    }
+
+    private fun TextLayoutResult.getLineStartByOffsetForLayout(
+        currentOffset: Int = transformedMinOffset()
+    ): Int {
+        val currentLine = getLineForOffset(currentOffset)
+        return offsetMapping.transformedToOriginal(getLineStart(currentLine))
+    }
+
+    private fun TextLayoutResult.getLineEndByOffsetForLayout(
+        currentOffset: Int = transformedMaxOffset()
+    ): Int {
+        val currentLine = getLineForOffset(currentOffset)
+        return offsetMapping.transformedToOriginal(getLineEnd(currentLine, true))
+    }
+
+    private fun TextLayoutResult.jumpByLinesOffset(linesAmount: Int): Int {
+        val currentOffset = transformedEndOffset()
+
+        if (state.cachedX == null) {
+            state.cachedX = getCursorRect(currentOffset).left
+        }
+
+        val targetLine = getLineForOffset(currentOffset) + linesAmount
+        when {
+            targetLine < 0 -> {
+                return 0
+            }
+            targetLine >= lineCount -> {
+                return text.length
+            }
+        }
+
+        val y = getLineBottom(targetLine) - 1
+        val x = state.cachedX!!.also {
+            if ((isLtr() && it >= getLineRight(targetLine)) ||
+                (!isLtr() && it <= getLineLeft(targetLine))
+            ) {
+                return getLineEnd(targetLine, true)
+            }
+        }
+
+        val newOffset = getOffsetForPosition(Offset(x, y)).let {
+            offsetMapping.transformedToOriginal(it)
+        }
+
+        return newOffset
+    }
+
+    private fun transformedEndOffset(): Int {
+        return offsetMapping.originalToTransformed(selection.end)
+    }
+
+    private fun transformedMinOffset(): Int {
+        return offsetMapping.originalToTransformed(selection.min)
+    }
+
+    private fun transformedMaxOffset(): Int {
+        return offsetMapping.originalToTransformed(selection.max)
+    }
+
+    private fun charOffset(offset: Int) =
+        offset.coerceAtMost(text.length - 1)
+
+    private fun getParagraphStart() = text.findParagraphStart(selection.min)
+
+    private fun getParagraphEnd() = text.findParagraphEnd(selection.max)
+
+    companion object {
+        /**
+         * Value returned by [getNextCharacterIndex] and [getPrecedingCharacterIndex] when no valid
+         * index could be found, e.g. it would be the end of the string.
+         *
+         * This is equivalent to `BreakIterator.DONE` on JVM/Android.
+         */
+        const val NoCharacterFound = -1
+    }
+}
+
+internal class TextPreparedSelection(
+    originalText: AnnotatedString,
+    originalSelection: TextRange,
+    layoutResult: TextLayoutResult? = null,
+    offsetMapping: OffsetMapping = OffsetMapping.Identity,
+    state: TextPreparedSelectionState = TextPreparedSelectionState()
+) : BaseTextPreparedSelection<TextPreparedSelection>(
+    originalText = originalText,
+    originalSelection = originalSelection,
+    layoutResult = layoutResult,
+    offsetMapping = offsetMapping,
+    state = state
+)
+
+internal class TextFieldPreparedSelection(
+    val currentValue: TextFieldValue,
+    offsetMapping: OffsetMapping = OffsetMapping.Identity,
+    val layoutResultProxy: TextLayoutResultProxy?,
+    state: TextPreparedSelectionState = TextPreparedSelectionState()
+) : BaseTextPreparedSelection<TextFieldPreparedSelection>(
+    originalText = currentValue.annotatedString,
+    originalSelection = currentValue.selection,
+    offsetMapping = offsetMapping,
+    layoutResult = layoutResultProxy?.value,
+    state = state
+) {
+    val value
+        get() = currentValue.copy(
+            annotatedString = annotatedString,
+            selection = selection
+        )
+
+    fun deleteIfSelectedOr(or: TextFieldPreparedSelection.() -> EditCommand?): List<EditCommand>? {
+        return if (selection.collapsed) {
+            or(this)?.let {
+                listOf(it)
+            }
+        } else {
+            listOf(
+                CommitTextCommand("", 0),
+                SetSelectionCommand(selection.min, selection.min)
+            )
+        }
+    }
+
+    fun moveCursorUpByPage() = apply(false) {
+        layoutResultProxy?.jumpByPagesOffset(-1)?.let { setCursor(it) }
+    }
+
+    fun moveCursorDownByPage() = apply(false) {
+        layoutResultProxy?.jumpByPagesOffset(1)?.let { setCursor(it) }
+    }
+
+    /**
+     * Returns a cursor position after jumping back or forth by [pagesAmount] number of pages,
+     * where `page` is the visible amount of space in the text field
+     */
+    private fun TextLayoutResultProxy.jumpByPagesOffset(pagesAmount: Int): Int {
+        val visibleInnerTextFieldRect = innerTextFieldCoordinates?.let { inner ->
+            decorationBoxCoordinates?.localBoundingBoxOf(inner)
+        } ?: Rect.Zero
+        val currentOffset = offsetMapping.originalToTransformed(currentValue.selection.end)
+        val currentPos = value.getCursorRect(currentOffset)
+        val x = currentPos.left
+        val y = currentPos.top + visibleInnerTextFieldRect.size.height * pagesAmount
+        return offsetMapping.transformedToOriginal(
+            value.getOffsetForPosition(Offset(x, y))
+        )
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/TextSelectionColors.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/TextSelectionColors.kt
new file mode 100644
index 0000000..6cc76ee
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/TextSelectionColors.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.compose.foundation.newtext.text.copypasta.selection
+
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.ui.graphics.Color
+
+/**
+ * Represents the colors used for text selection by text and text field components.
+ *
+ * See [LocalTextSelectionColors] to provide new values for this throughout the hierarchy.
+ *
+ * @property handleColor the color used for the selection handles on either side of the
+ * selection region.
+ * @property backgroundColor the color used to draw the background behind the selected
+ * region. This color should have alpha applied to keep the text legible - this alpha is
+ * typically 0.4f (40%) but this may need to be reduced in order to meet contrast requirements
+ * depending on the color used for text, selection background, and the background behind the
+ * selection background.
+ */
+@Immutable
+class TextSelectionColors(
+    val handleColor: Color,
+    val backgroundColor: Color
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is TextSelectionColors) return false
+
+        if (handleColor != other.handleColor) return false
+        if (backgroundColor != other.backgroundColor) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = handleColor.hashCode()
+        result = 31 * result + backgroundColor.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "SelectionColors(selectionHandleColor=$handleColor, " +
+            "selectionBackgroundColor=$backgroundColor)"
+    }
+}
+
+/**
+ * CompositionLocal used to change the [TextSelectionColors] used by text and text field
+ * components in the hierarchy.
+ */
+val LocalTextSelectionColors = compositionLocalOf { DefaultTextSelectionColors }
+
+/**
+ * Default color used is the blue from the Compose logo, b/172679845 for context
+ */
+private val DefaultSelectionColor = Color(0xFF4286F4)
+
+@Stable
+private val DefaultTextSelectionColors = TextSelectionColors(
+    handleColor = DefaultSelectionColor,
+    backgroundColor = DefaultSelectionColor.copy(alpha = 0.4f)
+)
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/TextSelectionDelegate.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/TextSelectionDelegate.kt
new file mode 100644
index 0000000..5a530ec
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/TextSelectionDelegate.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.newtext.text.copypasta.selection
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.text.TextLayoutResult
+import kotlin.math.max
+
+/**
+ * This method returns the graphical position where the selection handle should be based on the
+ * offset and other information.
+ *
+ * @param textLayoutResult a result of the text layout.
+ * @param offset character offset to be calculated
+ * @param isStart true if called for selection start handle
+ * @param areHandlesCrossed true if the selection handles are crossed
+ *
+ * @return the graphical position where the selection handle should be.
+ */
+internal fun getSelectionHandleCoordinates(
+    textLayoutResult: TextLayoutResult,
+    offset: Int,
+    isStart: Boolean,
+    areHandlesCrossed: Boolean
+): Offset {
+    val line = textLayoutResult.getLineForOffset(offset)
+    val x = textLayoutResult.getHorizontalPosition(offset, isStart, areHandlesCrossed)
+    val y = textLayoutResult.getLineBottom(line)
+
+    return Offset(x, y)
+}
+
+internal fun TextLayoutResult.getHorizontalPosition(
+    offset: Int,
+    isStart: Boolean,
+    areHandlesCrossed: Boolean
+): Float {
+    val offsetToCheck =
+        if (isStart && !areHandlesCrossed || !isStart && areHandlesCrossed) offset
+        else max(offset - 1, 0)
+    val bidiRunDirection = getBidiRunDirection(offsetToCheck)
+    val paragraphDirection = getParagraphDirection(offset)
+
+    return getHorizontalPosition(
+        offset = offset,
+        usePrimaryDirection = bidiRunDirection == paragraphDirection
+    )
+}
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/TextSelectionMouseDetector.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/TextSelectionMouseDetector.kt
new file mode 100644
index 0000000..3931bc4
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/TextSelectionMouseDetector.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.compose.foundation.newtext.text.copypasta.selection
+
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.drag
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.AwaitPointerEventScope
+import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.input.pointer.PointerType
+import androidx.compose.ui.input.pointer.changedToDown
+import androidx.compose.ui.input.pointer.isPrimaryPressed
+import androidx.compose.ui.input.pointer.isShiftPressed
+import androidx.compose.ui.platform.ViewConfiguration
+import androidx.compose.ui.util.fastAll
+
+// * Without shift it starts the new selection from the scratch.
+// * With shift expand / shrink existed selection.
+// * Click sets start and end of the selection, but shift click only the end of
+// selection.
+// * The specific case of it when selection is collapsed, but the same logic is
+// applied for not collapsed selection too.
+internal interface MouseSelectionObserver {
+    // on start of shift click. if returns true event will be consumed
+    fun onExtend(downPosition: Offset): Boolean
+    // on drag after shift click. if returns true event will be consumed
+    fun onExtendDrag(dragPosition: Offset): Boolean
+
+    // if returns true event will be consumed
+    fun onStart(downPosition: Offset, adjustment: SelectionAdjustment): Boolean
+    fun onDrag(dragPosition: Offset, adjustment: SelectionAdjustment): Boolean
+}
+
+// Distance in pixels between consecutive click positions to be considered them as clicks sequence
+internal const val ClicksSlop = 100.0
+
+private class ClicksCounter(
+    private val viewConfiguration: ViewConfiguration
+) {
+    var clicks = 0
+    var prevClick: PointerInputChange? = null
+    fun update(event: PointerEvent) {
+        val currentPrevClick = prevClick
+        val newClick = event.changes[0]
+        if (currentPrevClick != null &&
+            timeIsTolerable(currentPrevClick, newClick) &&
+            positionIsTolerable(currentPrevClick, newClick)
+        ) {
+            clicks += 1
+        } else {
+            clicks = 1
+        }
+        prevClick = newClick
+    }
+
+    fun timeIsTolerable(prevClick: PointerInputChange, newClick: PointerInputChange): Boolean {
+        val diff = newClick.uptimeMillis - prevClick.uptimeMillis
+        return diff < viewConfiguration.doubleTapTimeoutMillis
+    }
+
+    fun positionIsTolerable(prevClick: PointerInputChange, newClick: PointerInputChange): Boolean {
+        val diff = newClick.position - prevClick.position
+        return diff.getDistance() < ClicksSlop
+    }
+}
+
+internal suspend fun PointerInputScope.mouseSelectionDetector(
+    observer: MouseSelectionObserver
+) {
+    awaitEachGesture {
+        val clicksCounter = ClicksCounter(viewConfiguration)
+        while (true) {
+            val down: PointerEvent = awaitMouseEventDown()
+            clicksCounter.update(down)
+            val downChange = down.changes[0]
+            if (down.keyboardModifiers.isShiftPressed) {
+                val started = observer.onExtend(downChange.position)
+                if (started) {
+                    downChange.consume()
+                    drag(downChange.id) {
+                        if (observer.onExtendDrag(it.position)) {
+                            it.consume()
+                        }
+                    }
+                }
+            } else {
+                val selectionMode = when (clicksCounter.clicks) {
+                    1 -> SelectionAdjustment.None
+                    2 -> SelectionAdjustment.Word
+                    else -> SelectionAdjustment.Paragraph
+                }
+                val started = observer.onStart(downChange.position, selectionMode)
+                if (started) {
+                    downChange.consume()
+                    drag(downChange.id) {
+                        if (observer.onDrag(it.position, selectionMode)) {
+                            it.consume()
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+private suspend fun AwaitPointerEventScope.awaitMouseEventDown(): PointerEvent {
+    var event: PointerEvent
+    do {
+        event = awaitPointerEvent(PointerEventPass.Main)
+    } while (
+        !(
+            event.buttons.isPrimaryPressed && event.changes.fastAll {
+                it.type == PointerType.Mouse && it.changedToDown()
+            }
+            )
+    )
+    return event
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/MinMaxLinesCoercer.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/MinMaxLinesCoercer.kt
new file mode 100644
index 0000000..c616ac9
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/MinMaxLinesCoercer.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.newtext.text.modifiers
+
+import androidx.compose.ui.text.Paragraph
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.resolveDefaults
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import kotlin.math.roundToInt
+
+internal class MinMaxLinesCoercer private constructor(
+    val layoutDirection: LayoutDirection,
+    val inputTextStyle: TextStyle,
+    val density: Density,
+    val fontFamilyResolver: FontFamily.Resolver
+) {
+    private val resolvedStyle = resolveDefaults(inputTextStyle, layoutDirection)
+    private var lineHeightCache: Float = Float.NaN
+    private var oneLineHeightCache: Float = Float.NaN
+
+    companion object {
+        // LRU cache of one since this tends to be used for similar styles
+        // ... it may be useful to increase this cache if requested by some dev use case
+        private var last: MinMaxLinesCoercer? = null
+
+        /**
+         * Returns a coercer (possibly cached) with these parameters
+         */
+        fun from(
+            minMaxUtil: MinMaxLinesCoercer?,
+            layoutDirection: LayoutDirection,
+            paramStyle: TextStyle,
+            density: Density,
+            fontFamilyResolver: FontFamily.Resolver
+        ): MinMaxLinesCoercer {
+            minMaxUtil?.let {
+                if (layoutDirection == it.layoutDirection &&
+                    paramStyle == it.inputTextStyle &&
+                    density.density == it.density.density &&
+                    fontFamilyResolver === it.fontFamilyResolver) {
+                    return it
+                }
+            }
+            last?.let {
+                if (layoutDirection == it.layoutDirection &&
+                    paramStyle == it.inputTextStyle &&
+                    density.density == it.density.density &&
+                    fontFamilyResolver === it.fontFamilyResolver) {
+                    return it
+                }
+            }
+            return MinMaxLinesCoercer(
+                layoutDirection,
+                resolveDefaults(paramStyle, layoutDirection),
+                density,
+                fontFamilyResolver
+            ).also {
+                last = it
+            }
+        }
+    }
+
+    /**
+     * Coerce inConstraints to have min and max lines applied.
+     *
+     * On first invocation this will cause (2) Paragraph measurements.
+     */
+    internal fun coerceMaxMinLines(
+        inConstraints: Constraints,
+        minLines: Int,
+        maxLines: Int,
+    ): Constraints {
+        var oneLineHeight = oneLineHeightCache
+        var lineHeight = lineHeightCache
+        if (oneLineHeight.isNaN() || lineHeight.isNaN()) {
+            oneLineHeight = Paragraph(
+                text = EmptyTextReplacement,
+                style = resolvedStyle,
+                constraints = Constraints(),
+                density = density,
+                fontFamilyResolver = fontFamilyResolver,
+                maxLines = 1,
+                ellipsis = false
+            ).height
+
+            val twoLineHeight = Paragraph(
+                text = TwoLineTextReplacement,
+                style = resolvedStyle,
+                constraints = Constraints(),
+                density = density,
+                fontFamilyResolver = fontFamilyResolver,
+                maxLines = 2,
+                ellipsis = false
+            ).height
+
+            lineHeight = twoLineHeight - oneLineHeight
+            oneLineHeightCache = oneLineHeight
+            lineHeightCache = lineHeight
+        }
+        val maxHeight = if (maxLines != Int.MAX_VALUE) {
+            (oneLineHeight + (lineHeight * (maxLines - 1)))
+                .roundToInt()
+                .coerceAtLeast(0)
+        } else {
+            inConstraints.maxHeight
+        }
+        val minHeight = if (minLines != 1) {
+            (oneLineHeight + (lineHeight * (minLines - 1)))
+                .roundToInt()
+                .coerceAtLeast(0)
+                .coerceAtMost(maxHeight)
+        } else {
+            inConstraints.minHeight
+        }
+        return Constraints(
+            minHeight = minHeight,
+            maxHeight = maxHeight,
+            minWidth = inConstraints.minWidth,
+            maxWidth = inConstraints.maxWidth,
+        )
+    }
+}
+
+private const val DefaultWidthCharCount = 10 // min width for TextField is 10 chars long
+private val EmptyTextReplacement = "H".repeat(DefaultWidthCharCount) // just a reference character.
+private val TwoLineTextReplacement = EmptyTextReplacement + "\n" + EmptyTextReplacement
+
+internal fun validateMinMaxLines(minLines: Int, maxLines: Int) {
+    require(minLines > 0 && maxLines > 0) {
+        "both minLines $minLines and maxLines $maxLines must be greater than zero"
+    }
+    require(minLines <= maxLines) {
+        "minLines $minLines must be less than or equal to maxLines $maxLines"
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/MinMaxLinesUtils.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/MinMaxLinesUtils.kt
deleted file mode 100644
index 8310af8..0000000
--- a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/MinMaxLinesUtils.kt
+++ /dev/null
@@ -1,92 +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.compose.foundation.newtext.text.modifiers
-
-import androidx.compose.ui.text.Paragraph
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.resolveDefaults
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.LayoutDirection
-import kotlin.math.roundToInt
-
-internal fun Constraints.coerceMaxMinLines(
-    layoutDirection: LayoutDirection,
-    minLines: Int,
-    maxLines: Int,
-    paramStyle: TextStyle,
-    density: Density,
-    fontFamilyResolver: FontFamily.Resolver,
-): Constraints {
-    val style = resolveDefaults(paramStyle, layoutDirection)
-    val oneLine = Paragraph(
-        text = EmptyTextReplacement,
-        style = style,
-        constraints = Constraints(),
-        density = density,
-        fontFamilyResolver = fontFamilyResolver,
-        maxLines = 1,
-        ellipsis = false
-    ).height
-
-    val twoLines = Paragraph(
-        text = TwoLineTextReplacement,
-        style = style,
-        constraints = Constraints(),
-        density = density,
-        fontFamilyResolver = fontFamilyResolver,
-        maxLines = 2,
-        ellipsis = false
-    ).height
-
-    val lineHeight = twoLines - oneLine
-    val maxHeight = if (maxLines != Int.MAX_VALUE) {
-        (oneLine + (lineHeight * (maxLines - 1)))
-            .roundToInt()
-            .coerceAtLeast(0)
-    } else {
-        this.maxHeight
-    }
-    val minHeight = if (minLines != 1) {
-        (oneLine + (lineHeight * (minLines - 1)))
-            .roundToInt()
-            .coerceAtLeast(0)
-            .coerceAtMost(maxHeight)
-    } else {
-        this.minHeight
-    }
-    return Constraints(
-        minHeight = minHeight,
-        maxHeight = maxHeight,
-        minWidth = minWidth,
-        maxWidth = maxWidth,
-    )
-}
-
-private const val DefaultWidthCharCount = 10 // min width for TextField is 10 chars long
-private val EmptyTextReplacement = "H".repeat(DefaultWidthCharCount) // just a reference character.
-private val TwoLineTextReplacement = EmptyTextReplacement + "\n" + EmptyTextReplacement
-
-internal fun validateMinMaxLines(minLines: Int, maxLines: Int) {
-    require(minLines > 0 && maxLines > 0) {
-        "both minLines $minLines and maxLines $maxLines must be greater than zero"
-    }
-    require(minLines <= maxLines) {
-        "minLines $minLines must be less than or equal to maxLines $maxLines"
-    }
-}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/MultiParagraphLayoutCache.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/MultiParagraphLayoutCache.kt
index 544e608..fa494d1 100644
--- a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/MultiParagraphLayoutCache.kt
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/MultiParagraphLayoutCache.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * 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.
@@ -16,14 +16,16 @@
 
 package androidx.compose.foundation.newtext.text.modifiers
 
+import androidx.compose.foundation.newtext.text.DefaultMinLines
 import androidx.compose.foundation.newtext.text.ceilToIntPx
 import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.ExperimentalTextApi
 import androidx.compose.ui.text.MultiParagraph
 import androidx.compose.ui.text.MultiParagraphIntrinsics
 import androidx.compose.ui.text.Placeholder
 import androidx.compose.ui.text.TextLayoutInput
 import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.resolveDefaults
 import androidx.compose.ui.text.style.LineBreak
 import androidx.compose.ui.text.style.TextOverflow
@@ -34,17 +36,38 @@
 import androidx.compose.ui.unit.constrain
 
 internal class MultiParagraphLayoutCache(
-    private val params: TextInlineContentLayoutDrawParams,
-    private val density: Density,
-    private val placeholders: List<AnnotatedString.Range<Placeholder>> = emptyList()
+    private var text: AnnotatedString,
+    private var style: TextStyle,
+    private var fontFamilyResolver: FontFamily.Resolver,
+    private var overflow: TextOverflow = TextOverflow.Clip,
+    private var softWrap: Boolean = true,
+    private var maxLines: Int = Int.MAX_VALUE,
+    private var minLines: Int = DefaultMinLines,
+    private var placeholders: List<AnnotatedString.Range<Placeholder>>? = null,
 ) {
+    private var minMaxLinesCoercer: MinMaxLinesCoercer? = null
+    internal var density: Density? = null
+        set(value) {
+            val localField = field
+            if (value == null || localField == null) {
+                field = value
+                return
+            }
+
+            if (localField.density != value.density || localField.fontScale != value.fontScale) {
+                field = value
+                // none of our results are correct if density changed
+                markDirty()
+            }
+        }
+
     /*@VisibleForTesting*/
     // NOTE(text-perf-review): it seems like TextDelegate essentially guarantees that we use
     // MultiParagraph. Can we have a fast-path that uses just Paragraph in simpler cases (ie,
     // String)?
-    internal var paragraphIntrinsics: MultiParagraphIntrinsics? = null
+    private var paragraphIntrinsics: MultiParagraphIntrinsics? = null
 
-    internal var intrinsicsLayoutDirection: LayoutDirection? = null
+    private var intrinsicsLayoutDirection: LayoutDirection? = null
 
     private var layoutCache: TextLayoutResult? = null
     private var cachedIntrinsicHeight: Pair<Int, Int>? = null
@@ -76,6 +99,120 @@
         get() = layoutCache
 
     /**
+     * Update layout constraints for this text
+     *
+     * @return true if constraints caused a text layout invalidation
+     */
+    fun layoutWithConstraints(
+        constraints: Constraints,
+        layoutDirection: LayoutDirection
+    ): Boolean {
+        if (!layoutCache.newLayoutWillBeDifferent(constraints, layoutDirection)) {
+            return false
+        }
+        val finalConstraints = if (maxLines != Int.MAX_VALUE || minLines > 1) {
+            val localMinMax = MinMaxLinesCoercer.from(
+                minMaxLinesCoercer,
+                layoutDirection,
+                style,
+                density!!,
+                fontFamilyResolver
+            ).also {
+                minMaxLinesCoercer = it
+            }
+            localMinMax.coerceMaxMinLines(
+                inConstraints = constraints,
+                minLines = minLines,
+                maxLines = maxLines
+            )
+        } else {
+            constraints
+        }
+        val multiParagraph = layoutText(finalConstraints, layoutDirection)
+
+        val size = finalConstraints.constrain(
+            IntSize(
+                multiParagraph.width.ceilToIntPx(),
+                multiParagraph.height.ceilToIntPx()
+            )
+        )
+
+        layoutCache = TextLayoutResult(
+            TextLayoutInput(
+                text,
+                style,
+                placeholders.orEmpty(),
+                maxLines,
+                softWrap,
+                overflow,
+                density!!,
+                layoutDirection,
+                fontFamilyResolver,
+                finalConstraints
+            ),
+            multiParagraph,
+            size
+        )
+        return true
+    }
+
+    fun intrinsicHeightAt(width: Int, layoutDirection: LayoutDirection): Int {
+        cachedIntrinsicHeight?.let { (prevWidth, prevHeight) ->
+            if (width == prevWidth) return prevHeight
+        }
+        val result = layoutText(
+            Constraints(0, width, 0, Constraints.Infinity),
+            layoutDirection
+        ).height.ceilToIntPx()
+
+        cachedIntrinsicHeight = width to result
+        return result
+    }
+
+    fun update(
+        text: AnnotatedString,
+        style: TextStyle,
+        fontFamilyResolver: FontFamily.Resolver,
+        overflow: TextOverflow,
+        softWrap: Boolean,
+        maxLines: Int,
+        minLines: Int,
+        placeholders: List<AnnotatedString.Range<Placeholder>>?
+    ) {
+        this.text = text
+        this.style = style
+        this.fontFamilyResolver = fontFamilyResolver
+        this.overflow = overflow
+        this.softWrap = softWrap
+        this.maxLines = maxLines
+        this.minLines = minLines
+        this.placeholders = placeholders
+        markDirty()
+    }
+
+    private fun setLayoutDirection(layoutDirection: LayoutDirection) {
+        val localIntrinsics = paragraphIntrinsics
+        val intrinsics = if (
+            localIntrinsics == null ||
+            layoutDirection != intrinsicsLayoutDirection ||
+            localIntrinsics.hasStaleResolvedFonts
+        ) {
+            intrinsicsLayoutDirection = layoutDirection
+            MultiParagraphIntrinsics(
+                annotatedString = text,
+                style = resolveDefaults(style, layoutDirection),
+                density = density!!,
+                fontFamilyResolver = fontFamilyResolver,
+                placeholders = placeholders.orEmpty()
+            )
+        } else {
+            localIntrinsics
+        }
+
+        paragraphIntrinsics = intrinsics
+    }
+
+    /**
      * Computes the visual position of the glyphs for painting the text.
      *
      * The text will layout with a width that's as close to its max intrinsic width as possible
@@ -88,7 +225,7 @@
         setLayoutDirection(layoutDirection)
 
         val minWidth = constraints.minWidth
-        val widthMatters = params.softWrap || params.overflow == TextOverflow.Ellipsis
+        val widthMatters = softWrap || overflow == TextOverflow.Ellipsis
         val maxWidth = if (widthMatters && constraints.hasBoundedWidth) {
             constraints.maxWidth
         } else {
@@ -109,8 +246,8 @@
         //     AA…
         // Here we assume there won't be any '\n' character when softWrap is false. And make
         // maxLines 1 to implement the similar behavior.
-        val overwriteMaxLines = !params.softWrap && params.overflow == TextOverflow.Ellipsis
-        val finalMaxLines = if (overwriteMaxLines) 1 else params.maxLines.coerceAtLeast(1)
+        val overwriteMaxLines = !softWrap && overflow == TextOverflow.Ellipsis
+        val finalMaxLines = if (overwriteMaxLines) 1 else maxLines.coerceAtLeast(1)
 
         // if minWidth == maxWidth the width is fixed.
         //    therefore we can pass that value to our paragraph and use it
@@ -132,92 +269,20 @@
             constraints = Constraints(maxWidth = width, maxHeight = constraints.maxHeight),
             // This is a fallback behavior for ellipsis. Native
             maxLines = finalMaxLines,
-            ellipsis = params.overflow == TextOverflow.Ellipsis
+            ellipsis = overflow == TextOverflow.Ellipsis
         )
     }
 
-    private fun setLayoutDirection(layoutDirection: LayoutDirection) {
-        val localIntrinsics = paragraphIntrinsics
-        val intrinsics = if (
-            localIntrinsics == null ||
-            layoutDirection != intrinsicsLayoutDirection ||
-            localIntrinsics.hasStaleResolvedFonts
-        ) {
-            intrinsicsLayoutDirection = layoutDirection
-            MultiParagraphIntrinsics(
-                annotatedString = params.text,
-                style = resolveDefaults(params.style, layoutDirection),
-                density = density,
-                fontFamilyResolver = params.fontFamilyResolver,
-                placeholders = placeholders
-            )
-        } else {
-            localIntrinsics
-        }
-
-        paragraphIntrinsics = intrinsics
-    }
-
-    /**
-     * Update layout constraints for this text
-     *
-     * @return true if constraints caused a text layout invalidation
-     */
-    fun layoutWithConstraints(
-        constraints: Constraints,
-        layoutDirection: LayoutDirection
-    ): Boolean {
-        if (!layoutCache.newConstraintsProduceNewLayout(constraints, layoutDirection)) {
-            return false
-        }
-        val finalConstraints = if (params.maxLines != Int.MAX_VALUE || params.minLines >= 1) {
-            constraints.coerceMaxMinLines(
-                layoutDirection = layoutDirection,
-                minLines = params.minLines,
-                maxLines = params.maxLines,
-                paramStyle = params.style,
-                density = density,
-                fontFamilyResolver = params.fontFamilyResolver
-            )
-        } else {
-            constraints
-        }
-        val multiParagraph = layoutText(finalConstraints, layoutDirection)
-
-        val size = finalConstraints.constrain(
-            IntSize(
-                multiParagraph.width.ceilToIntPx(),
-                multiParagraph.height.ceilToIntPx()
-            )
-        )
-
-        layoutCache = TextLayoutResult(
-            TextLayoutInput(
-                params.text,
-                params.style,
-                placeholders,
-                params.maxLines,
-                params.softWrap,
-                params.overflow,
-                density,
-                layoutDirection,
-                params.fontFamilyResolver,
-                finalConstraints
-            ),
-            multiParagraph,
-            size
-        )
-        return true
-    }
-
-    @OptIn(ExperimentalTextApi::class)
-    private fun TextLayoutResult?.newConstraintsProduceNewLayout(
+    private fun TextLayoutResult?.newLayoutWillBeDifferent(
         constraints: Constraints,
         layoutDirection: LayoutDirection
     ): Boolean {
         // no layout yet
         if (this == null) return true
 
+        // async typeface changes
+        if (this.multiParagraph.intrinsics.hasStaleResolvedFonts) return true
+
         // layout direction changed
         if (layoutDirection != layoutInput.layoutDirection) return true
 
@@ -226,17 +291,17 @@
 
         // only be clever if we can predict line break behavior exactly, which is only possible with
         // simple geometry math for the greedy layout case
-        if (params.style.lineBreak != LineBreak.Simple) {
+        if (style.lineBreak != LineBreak.Simple) {
             return true
         }
 
         // see if width would produce the same wraps (greedy wraps only)
-        val canWrap = params.softWrap && params.maxLines > 1
+        val canWrap = softWrap && maxLines > 1
         if (canWrap && size.width != multiParagraph.maxIntrinsicWidth.ceilToIntPx()) {
             // some soft wrapping happened, check to see if we're between the previous measure and
             // the next wrap
-            val prevActualMaxWidth = params.paraMaxWidthFor(layoutInput.constraints)
-            val newMaxWidth = params.paraMaxWidthFor(constraints)
+            val prevActualMaxWidth = maxWidth(layoutInput.constraints)
+            val newMaxWidth = maxWidth(constraints)
             if (newMaxWidth > prevActualMaxWidth) {
                 // we've grown the potential layout area, and may break longer lines
                 return true
@@ -265,7 +330,7 @@
         return false
     }
 
-    private fun TextInlineContentLayoutDrawParams.paraMaxWidthFor(constraints: Constraints): Int {
+    private fun maxWidth(constraints: Constraints): Int {
         val minWidth = constraints.minWidth
         val widthMatters = softWrap || overflow == TextOverflow.Ellipsis
         val maxWidth = if (widthMatters && constraints.hasBoundedWidth) {
@@ -280,24 +345,8 @@
         }
     }
 
-    fun intrinsicHeightAt(width: Int, layoutDirection: LayoutDirection): Int {
-        cachedIntrinsicHeight?.let { (prevWidth, prevHeight) ->
-            if (width == prevWidth) return prevHeight
-        }
-        val result = layoutText(
-            Constraints(0, width, 0, Constraints.Infinity),
-            layoutDirection
-        ).height.ceilToIntPx()
-
-        cachedIntrinsicHeight = width to result
-        return result
-    }
-
-    fun equalForLayout(value: TextInlineContentLayoutDrawParams): Boolean {
-        return params.equalForLayout(value)
-    }
-
-    fun equalForCallbacks(value: TextInlineContentLayoutDrawParams): Boolean {
-        return params.equalForCallbacks(value)
+    private fun markDirty() {
+        paragraphIntrinsics = null
+        layoutCache = null
     }
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/SelectableTextAnnotatedStringElement.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/SelectableTextAnnotatedStringElement.kt
new file mode 100644
index 0000000..1514df6
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/SelectableTextAnnotatedStringElement.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.newtext.text.modifiers
+
+import androidx.compose.foundation.newtext.text.DefaultMinLines
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.Placeholder
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.style.TextOverflow
+
+@ExperimentalComposeUiApi
+internal class SelectableTextAnnotatedStringElement(
+    private val text: AnnotatedString,
+    private val style: TextStyle,
+    private val fontFamilyResolver: FontFamily.Resolver,
+    private val onTextLayout: ((TextLayoutResult) -> Unit)? = null,
+    private val overflow: TextOverflow = TextOverflow.Clip,
+    private val softWrap: Boolean = true,
+    private val maxLines: Int = Int.MAX_VALUE,
+    private val minLines: Int = DefaultMinLines,
+    private val placeholders: List<AnnotatedString.Range<Placeholder>>? = null,
+    private val onPlaceholderLayout: ((List<Rect?>) -> Unit)? = null,
+    private val selectionController: SelectionController? = null
+) : ModifierNodeElement<SelectableTextAnnotatedStringNode>(inspectorInfo = debugInspectorInfo { }) {
+    override fun create(): SelectableTextAnnotatedStringNode = SelectableTextAnnotatedStringNode(
+        text,
+        style,
+        fontFamilyResolver,
+        onTextLayout,
+        overflow,
+        softWrap,
+        maxLines,
+        minLines,
+        placeholders,
+        onPlaceholderLayout,
+        selectionController
+    )
+
+    override fun update(
+        node: SelectableTextAnnotatedStringNode
+    ): SelectableTextAnnotatedStringNode {
+        node.update(
+            text = text,
+            style = style,
+            placeholders = placeholders,
+            minLines = minLines,
+            maxLines = maxLines,
+            softWrap = softWrap,
+            fontFamilyResolver = fontFamilyResolver,
+            overflow = overflow,
+            onTextLayout = onTextLayout,
+            onPlaceholderLayout = onPlaceholderLayout,
+            selectionController = selectionController
+        )
+        return node
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+
+        if (other !is SelectableTextAnnotatedStringElement) return false
+
+        // these three are most likely to actually change
+        if (text != other.text) return false
+        if (style != other.style) return false
+        if (placeholders != other.placeholders) return false
+
+        // these are equally unlikely to change
+        if (fontFamilyResolver != other.fontFamilyResolver) return false
+        if (onTextLayout != other.onTextLayout) return false
+        if (overflow != other.overflow) return false
+        if (softWrap != other.softWrap) return false
+        if (maxLines != other.maxLines) return false
+        if (minLines != other.minLines) return false
+
+        // these never change, but check anyway for correctness
+        if (onPlaceholderLayout != other.onPlaceholderLayout) return false
+        if (selectionController != other.selectionController) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = super.hashCode()
+        result = 31 * result + text.hashCode()
+        result = 31 * result + style.hashCode()
+        result = 31 * result + fontFamilyResolver.hashCode()
+        result = 31 * result + (onTextLayout?.hashCode() ?: 0)
+        result = 31 * result + overflow.hashCode()
+        result = 31 * result + softWrap.hashCode()
+        result = 31 * result + maxLines
+        result = 31 * result + minLines
+        result = 31 * result + (placeholders?.hashCode() ?: 0)
+        result = 31 * result + (onPlaceholderLayout?.hashCode() ?: 0)
+        result = 31 * result + (selectionController?.hashCode() ?: 0)
+        return result
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/SelectableTextAnnotatedStringNode.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/SelectableTextAnnotatedStringNode.kt
new file mode 100644
index 0000000..c369c40
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/SelectableTextAnnotatedStringNode.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.newtext.text.modifiers
+
+import androidx.compose.foundation.newtext.text.DefaultMinLines
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.layout.IntrinsicMeasurable
+import androidx.compose.ui.layout.IntrinsicMeasureScope
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.GlobalPositionAwareModifierNode
+import androidx.compose.ui.node.LayoutModifierNode
+import androidx.compose.ui.node.SemanticsModifierNode
+import androidx.compose.ui.node.invalidateMeasurements
+import androidx.compose.ui.semantics.SemanticsConfiguration
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.Placeholder
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Constraints
+
+@OptIn(ExperimentalComposeUiApi::class)
+internal class SelectableTextAnnotatedStringNode(
+    text: AnnotatedString,
+    style: TextStyle,
+    fontFamilyResolver: FontFamily.Resolver,
+    onTextLayout: ((TextLayoutResult) -> Unit)? = null,
+    overflow: TextOverflow = TextOverflow.Clip,
+    softWrap: Boolean = true,
+    maxLines: Int = Int.MAX_VALUE,
+    minLines: Int = DefaultMinLines,
+    placeholders: List<AnnotatedString.Range<Placeholder>>? = null,
+    onPlaceholderLayout: ((List<Rect?>) -> Unit)? = null,
+    private val selectionController: SelectionController? = null
+) : DelegatingNode(), LayoutModifierNode, DrawModifierNode, GlobalPositionAwareModifierNode,
+    SemanticsModifierNode {
+
+    private val delegate = delegated {
+        TextAnnotatedStringNode(
+            text = text,
+            style = style,
+            fontFamilyResolver = fontFamilyResolver,
+            onTextLayout = onTextLayout,
+            overflow = overflow,
+            softWrap = softWrap,
+            maxLines = maxLines,
+            minLines = minLines,
+            placeholders = placeholders,
+            onPlaceholderLayout = onPlaceholderLayout,
+            selectionController = selectionController
+        )
+    }
+
+    init {
+        requireNotNull(selectionController) {
+            "Do not use SelectionCapableStaticTextModifier unless selectionController != null"
+        }
+    }
+
+    override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
+        selectionController?.updateGlobalPosition(coordinates)
+    }
+
+    override fun ContentDrawScope.draw() = delegate.drawNonExtension(this)
+
+    override fun MeasureScope.measure(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult = delegate.measureNonExtension(this, measurable, constraints)
+
+    override val semanticsConfiguration: SemanticsConfiguration
+        get() = delegate.semanticsConfiguration
+
+    override fun IntrinsicMeasureScope.minIntrinsicWidth(
+        measurable: IntrinsicMeasurable,
+        height: Int
+    ): Int = delegate.minIntrinsicWidthNonExtension(this, measurable, height)
+
+    override fun IntrinsicMeasureScope.minIntrinsicHeight(
+        measurable: IntrinsicMeasurable,
+        width: Int
+    ): Int = delegate.minIntrinsicHeightNonExtension(this, measurable, width)
+
+    override fun IntrinsicMeasureScope.maxIntrinsicWidth(
+        measurable: IntrinsicMeasurable,
+        height: Int
+    ): Int = delegate.maxIntrinsicWidthNonExtension(this, measurable, height)
+
+    override fun IntrinsicMeasureScope.maxIntrinsicHeight(
+        measurable: IntrinsicMeasurable,
+        width: Int
+    ): Int = delegate.maxIntrinsicHeightNonExtension(this, measurable, width)
+
+    fun update(
+        text: AnnotatedString,
+        style: TextStyle,
+        placeholders: List<AnnotatedString.Range<Placeholder>>?,
+        minLines: Int,
+        maxLines: Int,
+        softWrap: Boolean,
+        fontFamilyResolver: FontFamily.Resolver,
+        overflow: TextOverflow,
+        onTextLayout: ((TextLayoutResult) -> Unit)?,
+        onPlaceholderLayout: ((List<Rect?>) -> Unit)?,
+        selectionController: SelectionController?
+    ) {
+        delegate.doInvalidations(
+            textChanged = delegate.updateText(
+                text = text
+            ),
+            layoutChanged = delegate.updateLayoutRelatedArgs(
+                style = style,
+                placeholders = placeholders,
+                minLines = minLines,
+                maxLines = maxLines,
+                softWrap = softWrap,
+                fontFamilyResolver = fontFamilyResolver,
+                overflow = overflow
+            ),
+            callbacksChanged = delegate.updateCallbacks(
+                onTextLayout = onTextLayout,
+                onPlaceholderLayout = onPlaceholderLayout,
+                selectionController = selectionController
+            )
+        )
+        // we always relayout when we're selectable
+        invalidateMeasurements()
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/SelectionController.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/SelectionController.kt
new file mode 100644
index 0000000..ed21488
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/SelectionController.kt
@@ -0,0 +1,327 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.newtext.text.modifiers
+
+import androidx.compose.foundation.newtext.text.copypasta.TextDragObserver
+import androidx.compose.foundation.newtext.text.copypasta.detectDragGesturesAfterLongPressWithObserver
+import androidx.compose.foundation.newtext.text.copypasta.selection.MouseSelectionObserver
+import androidx.compose.foundation.newtext.text.copypasta.selection.MultiWidgetSelectionDelegate
+import androidx.compose.foundation.newtext.text.copypasta.selection.Selectable
+import androidx.compose.foundation.newtext.text.copypasta.selection.SelectionAdjustment
+import androidx.compose.foundation.newtext.text.copypasta.selection.SelectionRegistrar
+import androidx.compose.foundation.newtext.text.copypasta.selection.hasSelection
+import androidx.compose.foundation.newtext.text.copypasta.selection.mouseSelectionDetector
+import androidx.compose.foundation.newtext.text.copypasta.textPointerIcon
+import androidx.compose.runtime.RememberObserver
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.input.pointer.pointerHoverIcon
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.text.TextLayoutResult
+
+internal data class StaticTextSelectionParams(
+    val layoutCoordinates: LayoutCoordinates?,
+    val textLayoutResult: TextLayoutResult?
+) {
+    companion object {
+        val Empty = StaticTextSelectionParams(null, null)
+    }
+}
+
+// This is _basically_ a Modifier.Node but moved into remember because we need to do pointerInput
+// TODO: Refactor when Modifier.pointerInput is available for delegation
+internal class SelectionController(
+    private val selectionRegistrar: SelectionRegistrar,
+    private val backgroundSelectionColor: Color
+) : RememberObserver {
+    private var selectable: Selectable? = null
+    private val selectableId = selectionRegistrar.nextSelectableId()
+    // TODO: Move these into Modifer.element eventually
+    private var params: StaticTextSelectionParams = StaticTextSelectionParams.Empty
+
+    val modifier: Modifier = selectionRegistrar.makeSelectionModifier(
+        selectableId = selectableId,
+        layoutCoordinates = { params.layoutCoordinates },
+        textLayoutResult = { params.textLayoutResult },
+        // TODO: Use real isInTouchMode on merge
+        isInTouchMode = true /* fake it to android hardcode */
+    )
+
+    override fun onRemembered() {
+        selectable = selectionRegistrar.subscribe(
+            MultiWidgetSelectionDelegate(
+                selectableId = selectableId,
+                coordinatesCallback = { params.layoutCoordinates },
+                layoutResultCallback = { params.textLayoutResult }
+            )
+        )
+    }
+
+    override fun onForgotten() {
+        val localSelectable = selectable
+        if (localSelectable != null) {
+            selectionRegistrar.unsubscribe(localSelectable)
+            selectable = null
+        }
+    }
+
+    override fun onAbandoned() {
+        val localSelectable = selectable
+        if (localSelectable != null) {
+            selectionRegistrar.unsubscribe(localSelectable)
+            selectable = null
+        }
+    }
+
+    fun updateTextLayout(textLayoutResult: TextLayoutResult) {
+        params = params.copy(textLayoutResult = textLayoutResult)
+    }
+
+    fun updateGlobalPosition(coordinates: LayoutCoordinates) {
+        params = params.copy(layoutCoordinates = coordinates)
+    }
+
+    fun draw(contentDrawScope: ContentDrawScope) {
+        val layoutResult = params.textLayoutResult ?: return
+        val selection = selectionRegistrar.subselections[selectableId]
+
+        if (selection != null) {
+            val start = if (!selection.handlesCrossed) {
+                selection.start.offset
+            } else {
+                selection.end.offset
+            }
+            val end = if (!selection.handlesCrossed) {
+                selection.end.offset
+            } else {
+                selection.start.offset
+            }
+
+            if (start != end) {
+                val selectionPath = layoutResult.multiParagraph.getPathForRange(start, end)
+                with(contentDrawScope) {
+                    drawPath(selectionPath, backgroundSelectionColor)
+                }
+            }
+        }
+    }
+}
+
+// this is not chained, but is a standalone factory
+@Suppress("ModifierFactoryExtensionFunction")
+private fun SelectionRegistrar.makeSelectionModifier(
+    selectableId: Long,
+    layoutCoordinates: () -> LayoutCoordinates?,
+    textLayoutResult: () -> TextLayoutResult?,
+    isInTouchMode: Boolean
+): Modifier {
+    return if (isInTouchMode) {
+        val longPressDragObserver = object : TextDragObserver {
+            /**
+             * The beginning position of the drag gesture. Every time a new drag gesture starts, it wil be
+             * recalculated.
+             */
+            var lastPosition = Offset.Zero
+
+            /**
+             * The total distance being dragged of the drag gesture. Every time a new drag gesture starts,
+             * it will be zeroed out.
+             */
+            var dragTotalDistance = Offset.Zero
+
+            override fun onDown(point: Offset) {
+                // Not supported for long-press-drag.
+            }
+
+            override fun onUp() {
+                // Nothing to do.
+            }
+
+            override fun onStart(startPoint: Offset) {
+                layoutCoordinates()?.let {
+                    if (!it.isAttached) return
+
+                    if (textLayoutResult().outOfBoundary(startPoint, startPoint)) {
+                        notifySelectionUpdateSelectAll(
+                            selectableId = selectableId
+                        )
+                    } else {
+                        notifySelectionUpdateStart(
+                            layoutCoordinates = it,
+                            startPosition = startPoint,
+                            adjustment = SelectionAdjustment.Word
+                        )
+                    }
+
+                    lastPosition = startPoint
+                }
+                // selection never started
+                if (!hasSelection(selectableId)) return
+                // Zero out the total distance that being dragged.
+                dragTotalDistance = Offset.Zero
+            }
+
+            override fun onDrag(delta: Offset) {
+                layoutCoordinates()?.let {
+                    if (!it.isAttached) return
+                    // selection never started, did not consume any drag
+                    if (!hasSelection(selectableId)) return
+
+                    dragTotalDistance += delta
+                    val newPosition = lastPosition + dragTotalDistance
+
+                    if (!textLayoutResult().outOfBoundary(lastPosition, newPosition)) {
+                        // Notice that only the end position needs to be updated here.
+                        // Start position is left unchanged. This is typically important when
+                        // long-press is using SelectionAdjustment.WORD or
+                        // SelectionAdjustment.PARAGRAPH that updates the start handle position from
+                        // the dragBeginPosition.
+                        val consumed = notifySelectionUpdate(
+                            layoutCoordinates = it,
+                            previousPosition = lastPosition,
+                            newPosition = newPosition,
+                            isStartHandle = false,
+                            adjustment = SelectionAdjustment.CharacterWithWordAccelerate
+                        )
+                        if (consumed) {
+                            lastPosition = newPosition
+                            dragTotalDistance = Offset.Zero
+                        }
+                    }
+                }
+            }
+
+            override fun onStop() {
+                if (hasSelection(selectableId)) {
+                    notifySelectionUpdateEnd()
+                }
+            }
+
+            override fun onCancel() {
+                if (hasSelection(selectableId)) {
+                    notifySelectionUpdateEnd()
+                }
+            }
+        }
+        Modifier.pointerInput(longPressDragObserver) {
+            detectDragGesturesAfterLongPressWithObserver(
+                longPressDragObserver
+            )
+        }
+    } else {
+        val mouseSelectionObserver = object : MouseSelectionObserver {
+            var lastPosition = Offset.Zero
+
+            override fun onExtend(downPosition: Offset): Boolean {
+                layoutCoordinates()?.let { layoutCoordinates ->
+                    if (!layoutCoordinates.isAttached) return false
+                    val consumed = notifySelectionUpdate(
+                        layoutCoordinates = layoutCoordinates,
+                        newPosition = downPosition,
+                        previousPosition = lastPosition,
+                        isStartHandle = false,
+                        adjustment = SelectionAdjustment.None
+                    )
+                    if (consumed) {
+                        lastPosition = downPosition
+                    }
+                    return hasSelection(selectableId)
+                }
+                return false
+            }
+
+            override fun onExtendDrag(dragPosition: Offset): Boolean {
+                layoutCoordinates()?.let { layoutCoordinates ->
+                    if (!layoutCoordinates.isAttached) return false
+                    if (!hasSelection(selectableId)) return false
+
+                    val consumed = notifySelectionUpdate(
+                        layoutCoordinates = layoutCoordinates,
+                        newPosition = dragPosition,
+                        previousPosition = lastPosition,
+                        isStartHandle = false,
+                        adjustment = SelectionAdjustment.None
+                    )
+
+                    if (consumed) {
+                        lastPosition = dragPosition
+                    }
+                }
+                return true
+            }
+
+            override fun onStart(
+                downPosition: Offset,
+                adjustment: SelectionAdjustment
+            ): Boolean {
+                layoutCoordinates()?.let {
+                    if (!it.isAttached) return false
+
+                    notifySelectionUpdateStart(
+                        layoutCoordinates = it,
+                        startPosition = downPosition,
+                        adjustment = adjustment
+                    )
+
+                    lastPosition = downPosition
+                    return hasSelection(selectableId)
+                }
+
+                return false
+            }
+
+            override fun onDrag(
+                dragPosition: Offset,
+                adjustment: SelectionAdjustment
+            ): Boolean {
+                layoutCoordinates()?.let {
+                    if (!it.isAttached) return false
+                    if (!hasSelection(selectableId)) return false
+
+                    val consumed = notifySelectionUpdate(
+                        layoutCoordinates = it,
+                        previousPosition = lastPosition,
+                        newPosition = dragPosition,
+                        isStartHandle = false,
+                        adjustment = adjustment
+                    )
+                    if (consumed) {
+                        lastPosition = dragPosition
+                    }
+                }
+                return true
+            }
+        }
+        Modifier.pointerInput(mouseSelectionObserver) {
+            mouseSelectionDetector(mouseSelectionObserver)
+        }.pointerHoverIcon(textPointerIcon)
+    }
+}
+
+private fun TextLayoutResult?.outOfBoundary(start: Offset, end: Offset): Boolean {
+    this ?: return false
+
+    val lastOffset = layoutInput.text.text.length
+    val rawStartOffset = getOffsetForPosition(start)
+    val rawEndOffset = getOffsetForPosition(end)
+
+    return rawStartOffset >= lastOffset - 1 && rawEndOffset >= lastOffset - 1 ||
+        rawStartOffset < 0 && rawEndOffset < 0
+}
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/StaticTextModifier.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/StaticTextModifier.kt
deleted file mode 100644
index caff10e..0000000
--- a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/StaticTextModifier.kt
+++ /dev/null
@@ -1,109 +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.compose.foundation.newtext.text.modifiers
-
-import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.graphics.drawscope.ContentDrawScope
-import androidx.compose.ui.layout.IntrinsicMeasurable
-import androidx.compose.ui.layout.IntrinsicMeasureScope
-import androidx.compose.ui.layout.Measurable
-import androidx.compose.ui.layout.MeasureResult
-import androidx.compose.ui.layout.MeasureScope
-import androidx.compose.ui.node.DelegatingNode
-import androidx.compose.ui.node.DrawModifierNode
-import androidx.compose.ui.node.LayoutModifierNode
-import androidx.compose.ui.node.SemanticsModifierNode
-import androidx.compose.ui.semantics.SemanticsConfiguration
-import androidx.compose.ui.semantics.getTextLayoutResult
-import androidx.compose.ui.semantics.text
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.unit.Constraints
-
-@OptIn(ExperimentalComposeUiApi::class)
-internal class StaticTextModifier(
-    params: TextInlineContentLayoutDrawParams
-) : DelegatingNode(), LayoutModifierNode, DrawModifierNode, SemanticsModifierNode {
-
-    private val drawLayout = delegated { TextInlineContentLayoutDrawModifier(params) }
-
-    private var _semanticsConfiguration: SemanticsConfiguration = generateSemantics(params.text)
-
-    private val semanticsTextLayoutResult: (MutableList<TextLayoutResult>) -> Boolean =
-        { textLayoutResult ->
-            val layout = drawLayout.layoutOrNull?.also {
-                textLayoutResult.add(it)
-            }
-            layout != null
-        }
-
-    private fun generateSemantics(text: AnnotatedString): SemanticsConfiguration {
-        return SemanticsConfiguration().also {
-            it.isMergingSemanticsOfDescendants = false
-            it.isClearingSemantics = false
-            it.text = text
-            it.getTextLayoutResult(action = semanticsTextLayoutResult)
-        }
-    }
-
-    override val semanticsConfiguration: SemanticsConfiguration
-        get() = _semanticsConfiguration
-
-    fun update(params: TextInlineContentLayoutDrawParams) {
-        _semanticsConfiguration = generateSemantics(params.text)
-        drawLayout.params = params
-    }
-
-    override fun IntrinsicMeasureScope.minIntrinsicWidth(
-        measurable: IntrinsicMeasurable,
-        height: Int
-    ): Int {
-        return drawLayout.minIntrinsicWidthNonExtension(this, measurable, height)
-    }
-
-    override fun IntrinsicMeasureScope.minIntrinsicHeight(
-        measurable: IntrinsicMeasurable,
-        width: Int
-    ): Int {
-        return drawLayout.minIntrinsicHeightNonExtension(this, measurable, width)
-    }
-
-    override fun IntrinsicMeasureScope.maxIntrinsicWidth(
-        measurable: IntrinsicMeasurable,
-        height: Int
-    ): Int {
-        return drawLayout.maxIntrinsicWidthNonExtension(this, measurable, height)
-    }
-
-    override fun IntrinsicMeasureScope.maxIntrinsicHeight(
-        measurable: IntrinsicMeasurable,
-        width: Int
-    ): Int {
-        return drawLayout.maxIntrinsicHeightNonExtension(this, measurable, width)
-    }
-
-    override fun MeasureScope.measure(
-        measurable: Measurable,
-        constraints: Constraints
-    ): MeasureResult {
-        return drawLayout.measureNonExtension(this, measurable, constraints)
-    }
-
-    override fun ContentDrawScope.draw() {
-        drawLayout.drawNonExtension(this)
-    }
-}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/TextAnnotatedStringElement.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/TextAnnotatedStringElement.kt
new file mode 100644
index 0000000..1204125
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/TextAnnotatedStringElement.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.newtext.text.modifiers
+
+import androidx.compose.foundation.newtext.text.DefaultMinLines
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.Placeholder
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.style.TextOverflow
+
+@ExperimentalComposeUiApi
+internal class TextAnnotatedStringElement(
+    private val text: AnnotatedString,
+    private val style: TextStyle,
+    private val fontFamilyResolver: FontFamily.Resolver,
+    private val onTextLayout: ((TextLayoutResult) -> Unit)? = null,
+    private val overflow: TextOverflow = TextOverflow.Clip,
+    private val softWrap: Boolean = true,
+    private val maxLines: Int = Int.MAX_VALUE,
+    private val minLines: Int = DefaultMinLines,
+    private val placeholders: List<AnnotatedString.Range<Placeholder>>? = null,
+    private val onPlaceholderLayout: ((List<Rect?>) -> Unit)? = null,
+    private val selectionController: SelectionController? = null
+) : ModifierNodeElement<TextAnnotatedStringNode>(inspectorInfo = debugInspectorInfo { }) {
+    override fun create(): TextAnnotatedStringNode = TextAnnotatedStringNode(
+        text,
+        style,
+        fontFamilyResolver,
+        onTextLayout,
+        overflow,
+        softWrap,
+        maxLines,
+        minLines,
+        placeholders,
+        onPlaceholderLayout,
+        selectionController
+    )
+
+    override fun update(node: TextAnnotatedStringNode): TextAnnotatedStringNode {
+        node.doInvalidations(
+            textChanged = node.updateText(
+                text = text
+            ),
+            layoutChanged = node.updateLayoutRelatedArgs(
+                style = style,
+                placeholders = placeholders,
+                minLines = minLines,
+                maxLines = maxLines,
+                softWrap = softWrap,
+                fontFamilyResolver = fontFamilyResolver,
+                overflow = overflow
+            ),
+            callbacksChanged = node.updateCallbacks(
+                onTextLayout = onTextLayout,
+                onPlaceholderLayout = onPlaceholderLayout,
+                selectionController = selectionController
+            )
+        )
+        return node
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+
+        if (other !is TextAnnotatedStringElement) return false
+
+        // these three are most likely to actually change
+        if (text != other.text) return false
+        if (style != other.style) return false
+        if (placeholders != other.placeholders) return false
+
+        // these are equally unlikely to change
+        if (fontFamilyResolver != other.fontFamilyResolver) return false
+        if (onTextLayout != other.onTextLayout) return false
+        if (overflow != other.overflow) return false
+        if (softWrap != other.softWrap) return false
+        if (maxLines != other.maxLines) return false
+        if (minLines != other.minLines) return false
+
+        // these never change, but check anyway for correctness
+        if (onPlaceholderLayout != other.onPlaceholderLayout) return false
+        if (selectionController != other.selectionController) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = super.hashCode()
+        result = 31 * result + text.hashCode()
+        result = 31 * result + style.hashCode()
+        result = 31 * result + fontFamilyResolver.hashCode()
+        result = 31 * result + (onTextLayout?.hashCode() ?: 0)
+        result = 31 * result + overflow.hashCode()
+        result = 31 * result + softWrap.hashCode()
+        result = 31 * result + maxLines
+        result = 31 * result + minLines
+        result = 31 * result + (placeholders?.hashCode() ?: 0)
+        result = 31 * result + (onPlaceholderLayout?.hashCode() ?: 0)
+        result = 31 * result + (selectionController?.hashCode() ?: 0)
+        return result
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/TextAnnotatedStringNode.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/TextAnnotatedStringNode.kt
new file mode 100644
index 0000000..164c4fb
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/TextAnnotatedStringNode.kt
@@ -0,0 +1,344 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.newtext.text.modifiers
+
+import androidx.compose.foundation.newtext.text.DefaultMinLines
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.layout.AlignmentLine
+import androidx.compose.ui.layout.FirstBaseline
+import androidx.compose.ui.layout.IntrinsicMeasurable
+import androidx.compose.ui.layout.IntrinsicMeasureScope
+import androidx.compose.ui.layout.LastBaseline
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.LayoutModifierNode
+import androidx.compose.ui.node.SemanticsModifierNode
+import androidx.compose.ui.node.invalidateDraw
+import androidx.compose.ui.node.invalidateLayer
+import androidx.compose.ui.node.invalidateMeasurements
+import androidx.compose.ui.node.invalidateSemantics
+import androidx.compose.ui.semantics.SemanticsConfiguration
+import androidx.compose.ui.semantics.getTextLayoutResult
+import androidx.compose.ui.semantics.text
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.Placeholder
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextPainter
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import kotlin.math.roundToInt
+
+@OptIn(ExperimentalComposeUiApi::class)
+internal class TextAnnotatedStringNode(
+    private var text: AnnotatedString,
+    private var style: TextStyle,
+    private var fontFamilyResolver: FontFamily.Resolver,
+    private var onTextLayout: ((TextLayoutResult) -> Unit)? = null,
+    private var overflow: TextOverflow = TextOverflow.Clip,
+    private var softWrap: Boolean = true,
+    private var maxLines: Int = Int.MAX_VALUE,
+    private var minLines: Int = DefaultMinLines,
+    private var placeholders: List<AnnotatedString.Range<Placeholder>>? = null,
+    private var onPlaceholderLayout: ((List<Rect?>) -> Unit)? = null,
+    private var selectionController: SelectionController? = null
+) : Modifier.Node(), LayoutModifierNode, DrawModifierNode, SemanticsModifierNode {
+    private var baselineCache: Map<AlignmentLine, Int>? = null
+
+    private var _layoutCache: MultiParagraphLayoutCache? = null
+    private val layoutCache: MultiParagraphLayoutCache
+        get() {
+            if (_layoutCache == null) {
+                _layoutCache = MultiParagraphLayoutCache(
+                    text,
+                    style,
+                    fontFamilyResolver,
+                    overflow,
+                    softWrap,
+                    maxLines,
+                    minLines,
+                    placeholders
+                )
+            }
+            return _layoutCache!!
+        }
+
+    private fun getLayoutCache(density: Density): MultiParagraphLayoutCache {
+        return layoutCache.also { it.density = density }
+    }
+
+    fun updateText(text: AnnotatedString): Boolean {
+        if (this.text == text) return false
+        this.text = text
+        return true
+    }
+
+    fun updateLayoutRelatedArgs(
+        style: TextStyle,
+        placeholders: List<AnnotatedString.Range<Placeholder>>?,
+        minLines: Int,
+        maxLines: Int,
+        softWrap: Boolean,
+        fontFamilyResolver: FontFamily.Resolver,
+        overflow: TextOverflow
+    ): Boolean {
+        var changed = false
+        if (this.style != style) {
+            this.style = style
+            changed = true
+        }
+        if (this.placeholders != placeholders) {
+            this.placeholders = placeholders
+            changed = true
+        }
+
+        if (this.minLines != minLines) {
+            this.minLines = minLines
+            changed = true
+        }
+
+        if (this.maxLines != maxLines) {
+            this.maxLines != maxLines
+            changed = true
+        }
+
+        if (this.softWrap != softWrap) {
+            this.softWrap = softWrap
+            changed = true
+        }
+
+        if (this.fontFamilyResolver != fontFamilyResolver) {
+            this.fontFamilyResolver = fontFamilyResolver
+            changed = true
+        }
+
+        if (this.overflow != overflow) {
+            this.overflow = overflow
+            changed = true
+        }
+
+        return changed
+    }
+
+    fun updateCallbacks(
+        onTextLayout: ((TextLayoutResult) -> Unit)?,
+        onPlaceholderLayout: ((List<Rect?>) -> Unit)?,
+        selectionController: SelectionController?
+    ): Boolean {
+        var changed = false
+
+        if (this.onTextLayout != onTextLayout) {
+            this.onTextLayout = onTextLayout
+            changed = true
+        }
+
+        if (this.onPlaceholderLayout != onPlaceholderLayout) {
+            this.onPlaceholderLayout = onPlaceholderLayout
+            changed = true
+        }
+
+        if (this.selectionController != selectionController) {
+            this.selectionController = selectionController
+            changed = true
+        }
+        return changed
+    }
+
+    fun doInvalidations(
+        textChanged: Boolean,
+        layoutChanged: Boolean,
+        callbacksChanged: Boolean
+    ) {
+        if (textChanged) {
+            _semanticsConfiguration = null
+            invalidateSemantics()
+        }
+
+        if (textChanged || layoutChanged || callbacksChanged) {
+            layoutCache.update(
+                text = text,
+                style = style,
+                fontFamilyResolver = fontFamilyResolver,
+                overflow = overflow,
+                softWrap = softWrap,
+                maxLines = maxLines,
+                minLines = minLines,
+                placeholders = placeholders
+            )
+            invalidateMeasurements()
+            invalidateDraw()
+        }
+    }
+
+    private var _semanticsConfiguration: SemanticsConfiguration? = null
+
+    private var semanticsTextLayoutResult: ((MutableList<TextLayoutResult>) -> Boolean)? = null
+
+    private fun generateSemantics(text: AnnotatedString): SemanticsConfiguration {
+        var localSemanticsTextLayoutResult = semanticsTextLayoutResult
+        if (localSemanticsTextLayoutResult == null) {
+            localSemanticsTextLayoutResult = { textLayoutResult ->
+                val layout = layoutCache.layoutOrNull?.also {
+                    textLayoutResult.add(it)
+                }
+                layout != null
+            }
+            semanticsTextLayoutResult = localSemanticsTextLayoutResult
+        }
+        return SemanticsConfiguration().also {
+            it.isMergingSemanticsOfDescendants = false
+            it.isClearingSemantics = false
+            it.text = text
+            it.getTextLayoutResult(action = localSemanticsTextLayoutResult)
+        }
+    }
+
+    override val semanticsConfiguration: SemanticsConfiguration
+        get() {
+            var localSemantics = _semanticsConfiguration
+            if (localSemantics == null) {
+                localSemantics = generateSemantics(text)
+                _semanticsConfiguration = localSemantics
+            }
+            return localSemantics
+        }
+
+    fun measureNonExtension(
+        measureScope: MeasureScope,
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult {
+        return measureScope.measure(measurable, constraints)
+    }
+
+    override fun MeasureScope.measure(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult {
+        val layoutCache = getLayoutCache(this)
+
+        val didChangeLayout = layoutCache.layoutWithConstraints(constraints, layoutDirection)
+        val textLayoutResult = layoutCache.layout
+
+        // ensure measure restarts when hasStaleResolvedFonts by reading in measure
+        textLayoutResult.multiParagraph.intrinsics.hasStaleResolvedFonts
+
+        if (didChangeLayout) {
+            invalidateLayer()
+            onTextLayout?.invoke(textLayoutResult)
+            selectionController?.updateTextLayout(textLayoutResult)
+            baselineCache = mapOf(
+                FirstBaseline to textLayoutResult.firstBaseline.roundToInt(),
+                LastBaseline to textLayoutResult.lastBaseline.roundToInt()
+            )
+        }
+
+        // first share the placeholders
+        onPlaceholderLayout?.invoke(textLayoutResult.placeholderRects)
+
+        // then allow children to measure _inside_ our final box, with the above placeholders
+        val placeable = measurable.measure(
+            Constraints.fixed(
+                textLayoutResult.size.width,
+                textLayoutResult.size.height
+            )
+        )
+
+        return layout(
+            textLayoutResult.size.width,
+            textLayoutResult.size.height,
+            baselineCache!!
+        ) {
+            // this is basically a graphicsLayer
+            placeable.place(0, 0)
+        }
+    }
+
+    fun minIntrinsicWidthNonExtension(
+        intrinsicMeasureScope: IntrinsicMeasureScope,
+        measurable: IntrinsicMeasurable,
+        height: Int
+    ): Int {
+        return intrinsicMeasureScope.minIntrinsicWidth(measurable, height)
+    }
+
+    override fun IntrinsicMeasureScope.minIntrinsicWidth(
+        measurable: IntrinsicMeasurable,
+        height: Int
+    ): Int {
+        return getLayoutCache(this).minIntrinsicWidth
+    }
+
+    fun minIntrinsicHeightNonExtension(
+        intrinsicMeasureScope: IntrinsicMeasureScope,
+        measurable: IntrinsicMeasurable,
+        width: Int
+    ): Int {
+        return intrinsicMeasureScope.minIntrinsicHeight(measurable, width)
+    }
+
+    override fun IntrinsicMeasureScope.minIntrinsicHeight(
+        measurable: IntrinsicMeasurable,
+        width: Int
+    ): Int = getLayoutCache(this).intrinsicHeightAt(width, layoutDirection)
+
+    fun maxIntrinsicWidthNonExtension(
+        intrinsicMeasureScope: IntrinsicMeasureScope,
+        measurable: IntrinsicMeasurable,
+        height: Int
+    ): Int = intrinsicMeasureScope.maxIntrinsicWidth(measurable, height)
+
+    override fun IntrinsicMeasureScope.maxIntrinsicWidth(
+        measurable: IntrinsicMeasurable,
+        height: Int
+    ): Int = getLayoutCache(this).maxIntrinsicWidth
+
+    fun maxIntrinsicHeightNonExtension(
+        intrinsicMeasureScope: IntrinsicMeasureScope,
+        measurable: IntrinsicMeasurable,
+        width: Int
+    ): Int = intrinsicMeasureScope.maxIntrinsicHeight(measurable, width)
+
+    override fun IntrinsicMeasureScope.maxIntrinsicHeight(
+        measurable: IntrinsicMeasurable,
+        width: Int
+    ): Int = getLayoutCache(this).intrinsicHeightAt(width, layoutDirection)
+
+    fun drawNonExtension(
+        contentDrawScope: ContentDrawScope
+    ) {
+        return contentDrawScope.draw()
+    }
+
+    override fun ContentDrawScope.draw() {
+        selectionController?.draw(this)
+        drawIntoCanvas { canvas ->
+            TextPainter.paint(canvas, requireNotNull(layoutCache.layout))
+        }
+        if (!placeholders.isNullOrEmpty()) {
+            drawContent()
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/TextInlineContentLayoutDrawModifier.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/TextInlineContentLayoutDrawModifier.kt
deleted file mode 100644
index 1ff854e..0000000
--- a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/TextInlineContentLayoutDrawModifier.kt
+++ /dev/null
@@ -1,193 +0,0 @@
-package androidx.compose.foundation.newtext.text.modifiers
-
-import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.drawscope.ContentDrawScope
-import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
-import androidx.compose.ui.layout.FirstBaseline
-import androidx.compose.ui.layout.IntrinsicMeasurable
-import androidx.compose.ui.layout.IntrinsicMeasureScope
-import androidx.compose.ui.layout.LastBaseline
-import androidx.compose.ui.layout.Measurable
-import androidx.compose.ui.layout.MeasureResult
-import androidx.compose.ui.layout.MeasureScope
-import androidx.compose.ui.node.DrawModifierNode
-import androidx.compose.ui.node.LayoutModifierNode
-import androidx.compose.ui.node.invalidateDraw
-import androidx.compose.ui.node.invalidateLayout
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextPainter
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.Density
-import kotlin.math.roundToInt
-
-/**
- * Modifier that does Layout and Draw for [TextInlineContentLayoutDrawParams]
- */
-@OptIn(ExperimentalComposeUiApi::class)
-internal class TextInlineContentLayoutDrawModifier(
-    params: TextInlineContentLayoutDrawParams
-) : Modifier.Node(), LayoutModifierNode, DrawModifierNode {
-    private var layoutCache: MultiParagraphLayoutCache? = null
-    private var textDelegateDirty = true
-
-    val layoutOrNull: TextLayoutResult?
-        get() = layoutCache?.layoutOrNull
-
-    internal var params: TextInlineContentLayoutDrawParams = params
-        set(value) {
-            validate(params)
-            layoutCache?.let { cache ->
-                if (cache.equalForLayout(value) || cache.equalForCallbacks(value)) {
-                    textDelegateDirty = true
-                    invalidateLayout()
-                }
-            }
-            field = value
-            // if we set params, always redraw.
-            invalidateDraw()
-        }
-
-    private fun validate(params: TextInlineContentLayoutDrawParams) {
-        validateMinMaxLines(params.minLines, params.maxLines)
-    }
-
-    private fun getOrUpdateTextDelegateInLayout(
-        density: Density
-    ): MultiParagraphLayoutCache {
-        val localLayoutCache = layoutCache
-        return if (!textDelegateDirty && localLayoutCache != null) {
-            localLayoutCache
-        } else {
-            val textDelegate = MultiParagraphLayoutCache(params, density)
-            this.layoutCache = textDelegate
-            textDelegateDirty = false
-            textDelegate
-        }
-    }
-
-    fun measureNonExtension(
-        measureScope: MeasureScope,
-        measurable: Measurable,
-        constraints: Constraints
-    ): MeasureResult {
-        return measureScope.measure(measurable, constraints)
-    }
-
-    override fun MeasureScope.measure(
-        measurable: Measurable,
-        constraints: Constraints
-    ): MeasureResult {
-        val td = getOrUpdateTextDelegateInLayout(this)
-
-        val didChangeLayout = td.layoutWithConstraints(constraints, layoutDirection)
-        val textLayoutResult = td.layout
-
-        if (didChangeLayout) {
-            invalidateDraw()
-            params.onTextLayout?.invoke(textLayoutResult)
-        }
-
-        // first share the placeholders
-        params.onPlaceholderLayout?.invoke(textLayoutResult.placeholderRects)
-
-        // then allow children to measure _inside_ our final box, with the above placeholders
-        val placeable = measurable.measure(
-            Constraints.fixed(
-                textLayoutResult.size.width,
-                textLayoutResult.size.height
-            )
-        )
-
-        return layout(
-            textLayoutResult.size.width,
-            textLayoutResult.size.height,
-            mapOf(
-                FirstBaseline to textLayoutResult.firstBaseline.roundToInt(),
-                LastBaseline to textLayoutResult.lastBaseline.roundToInt()
-            )
-        ) {
-            // this is basically a graphicsLayer
-            placeable.placeWithLayer(0, 0)
-        }
-    }
-
-    fun minIntrinsicWidthNonExtension(
-        intrinsicMeasureScope: IntrinsicMeasureScope,
-        measurable: IntrinsicMeasurable,
-        height: Int
-    ): Int {
-        return intrinsicMeasureScope.minIntrinsicWidth(measurable, height)
-    }
-
-    override fun IntrinsicMeasureScope.minIntrinsicWidth(
-        measurable: IntrinsicMeasurable,
-        height: Int
-    ): Int {
-        val td = getOrUpdateTextDelegateInLayout(this)
-        return td.minIntrinsicWidth
-    }
-
-    fun minIntrinsicHeightNonExtension(
-        intrinsicMeasureScope: IntrinsicMeasureScope,
-        measurable: IntrinsicMeasurable,
-        width: Int
-    ): Int {
-        return intrinsicMeasureScope.minIntrinsicHeight(measurable, width)
-    }
-
-    override fun IntrinsicMeasureScope.minIntrinsicHeight(
-        measurable: IntrinsicMeasurable,
-        width: Int
-    ): Int {
-        return getOrUpdateTextDelegateInLayout(this)
-            .intrinsicHeightAt(width, layoutDirection)
-    }
-
-    fun maxIntrinsicWidthNonExtension(
-        intrinsicMeasureScope: IntrinsicMeasureScope,
-        measurable: IntrinsicMeasurable,
-        height: Int
-    ): Int {
-        return intrinsicMeasureScope.maxIntrinsicWidth(measurable, height)
-    }
-
-    override fun IntrinsicMeasureScope.maxIntrinsicWidth(
-        measurable: IntrinsicMeasurable,
-        height: Int
-    ): Int {
-        val td = getOrUpdateTextDelegateInLayout(this)
-        return td.maxIntrinsicWidth
-    }
-
-    fun maxIntrinsicHeightNonExtension(
-        intrinsicMeasureScope: IntrinsicMeasureScope,
-        measurable: IntrinsicMeasurable,
-        width: Int
-    ): Int {
-        return intrinsicMeasureScope.maxIntrinsicHeight(measurable, width)
-    }
-
-    override fun IntrinsicMeasureScope.maxIntrinsicHeight(
-        measurable: IntrinsicMeasurable,
-        width: Int
-    ): Int {
-        return getOrUpdateTextDelegateInLayout(this)
-            .intrinsicHeightAt(width, layoutDirection)
-    }
-
-    fun drawNonExtension(
-        contentDrawScope: ContentDrawScope
-    ) {
-        return contentDrawScope.draw()
-    }
-
-    override fun ContentDrawScope.draw() {
-        drawIntoCanvas { canvas ->
-            TextPainter.paint(canvas, requireNotNull(layoutCache?.layout))
-        }
-        if (!params.placeholders.isNullOrEmpty()) {
-            drawContent()
-        }
-    }
-}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/TextInlineContentLayoutDrawParams.kt b/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/TextInlineContentLayoutDrawParams.kt
deleted file mode 100644
index fa3bbeb..0000000
--- a/compose/foundation/foundation-newtext/src/commonMain/kotlin/androidx/compose/foundation/newtext/text/modifiers/TextInlineContentLayoutDrawParams.kt
+++ /dev/null
@@ -1,68 +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.compose.foundation.newtext.text.modifiers
-
-import androidx.compose.foundation.newtext.text.DefaultMinLines
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.Placeholder
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.style.TextOverflow
-
-// TODO(seanmcq): break this into (text, style) and (rest...) objects to avoid high-invalidation cost
-// TODO(seanmcq): Explore this holding non-AnnotatedString (future perf opt)
-internal data class TextInlineContentLayoutDrawParams(
-    val text: AnnotatedString,
-    val style: TextStyle,
-    val fontFamilyResolver: FontFamily.Resolver,
-    val onTextLayout: ((TextLayoutResult) -> Unit)? = null,
-    val overflow: TextOverflow = TextOverflow.Clip,
-    val softWrap: Boolean = true,
-    val maxLines: Int = Int.MAX_VALUE,
-    val minLines: Int = DefaultMinLines,
-    val placeholders: List<AnnotatedString.Range<Placeholder>>? = null,
-    val onPlaceholderLayout: ((List<Rect?>) -> Unit)? = null,
-) {
-    init {
-        validateMinMaxLines(minLines, maxLines)
-    }
-}
-
-internal fun TextInlineContentLayoutDrawParams.equalForCallbacks(
-    newParams: TextInlineContentLayoutDrawParams
-): Boolean {
-    return onTextLayout == newParams.onTextLayout &&
-        onPlaceholderLayout == newParams.onPlaceholderLayout
-}
-internal fun TextInlineContentLayoutDrawParams.equalForLayout(
-    newParams: TextInlineContentLayoutDrawParams
-): Boolean {
-    if (this === newParams) return true
-
-    if (text != newParams.text) return false
-    if (!style.hasSameLayoutAffectingAttributes(newParams.style)) return false
-    if (maxLines != newParams.maxLines) return false
-    if (minLines != newParams.minLines) return false
-    if (softWrap != newParams.softWrap) return false
-    if (fontFamilyResolver != newParams.fontFamilyResolver) return false
-    if (overflow != newParams.overflow) return false
-    if (placeholders != newParams.placeholders) return false
-
-    return true
-}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/BasicContextMenuRepresentation.desktop.kt b/compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/BasicContextMenuRepresentation.desktop.kt
new file mode 100644
index 0000000..86ddbd5
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/BasicContextMenuRepresentation.desktop.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Popup
+import androidx.compose.ui.window.rememberCursorPositionProvider
+
+// Design of basic represenation is from Material specs:
+// https://material.io/design/interaction/states.html#hover
+// https://material.io/components/menus#specs
+
+val LightDefaultContextMenuRepresentation = DefaultContextMenuRepresentation(
+    backgroundColor = Color.White,
+    textColor = Color.Black,
+    itemHoverColor = Color.Black.copy(alpha = 0.04f)
+)
+
+val DarkDefaultContextMenuRepresentation = DefaultContextMenuRepresentation(
+    backgroundColor = Color(0xFF121212), // like surface in darkColors
+    textColor = Color.White,
+    itemHoverColor = Color.White.copy(alpha = 0.04f)
+)
+
+class DefaultContextMenuRepresentation(
+    private val backgroundColor: Color,
+    private val textColor: Color,
+    private val itemHoverColor: Color
+) : ContextMenuRepresentation {
+    @Composable
+    override fun Representation(state: ContextMenuState, items: List<ContextMenuItem>) {
+        val isOpen = state.status is ContextMenuState.Status.Open
+        if (isOpen) {
+            Popup(
+                focusable = true,
+                onDismissRequest = { state.status = ContextMenuState.Status.Closed },
+                popupPositionProvider = rememberCursorPositionProvider()
+            ) {
+                Column(
+                    modifier = Modifier
+                        .shadow(8.dp)
+                        .background(backgroundColor)
+                        .padding(vertical = 4.dp)
+                        .width(IntrinsicSize.Max)
+                        .verticalScroll(rememberScrollState())
+
+                ) {
+                    items.distinctBy { it.label }.forEach { item ->
+                        MenuItemContent(
+                            itemHoverColor = itemHoverColor,
+                            onClick = {
+                                state.status = ContextMenuState.Status.Closed
+                                item.onClick()
+                            }
+                        ) {
+                            BasicText(text = item.label, style = TextStyle(color = textColor))
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+@Composable
+private fun MenuItemContent(
+    itemHoverColor: Color,
+    onClick: () -> Unit,
+    content: @Composable RowScope.() -> Unit
+) {
+    var hovered by remember { mutableStateOf(false) }
+    Row(
+        modifier = Modifier
+            .clickable(
+                onClick = onClick,
+            )
+            .onHover { hovered = it }
+            .background(if (hovered) itemHoverColor else Color.Transparent)
+            .fillMaxWidth()
+            // Preferred min and max width used during the intrinsic measurement.
+            .sizeIn(
+                minWidth = 112.dp,
+                maxWidth = 280.dp,
+                minHeight = 32.dp
+            )
+            .padding(
+                PaddingValues(
+                    horizontal = 16.dp,
+                    vertical = 0.dp
+                )
+            ),
+        verticalAlignment = Alignment.CenterVertically
+    ) {
+        content()
+    }
+}
+
+private fun Modifier.onHover(onHover: (Boolean) -> Unit) = pointerInput(Unit) {
+    awaitPointerEventScope {
+        while (true) {
+            val event = awaitPointerEvent()
+            when (event.type) {
+                PointerEventType.Enter -> onHover(true)
+                PointerEventType.Exit -> onHover(false)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/ContextMenuProvider.desktop.kt b/compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/ContextMenuProvider.desktop.kt
new file mode 100644
index 0000000..2a7fa89
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/ContextMenuProvider.desktop.kt
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation
+
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.ProvidableCompositionLocal
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.input.pointer.AwaitPointerEventScope
+import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.changedToDown
+import androidx.compose.ui.input.pointer.isSecondaryPressed
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.util.fastAll
+
+/**
+ * Defines a container where context menu is available. Menu is triggered by right mouse clicks.
+ * Representation of menu is defined by [LocalContextMenuRepresentation]`
+ *
+ * @param items List of context menu items. Final context menu contains all items from descendant
+ * [ContextMenuArea] and [ContextMenuDataProvider].
+ * @param state [ContextMenuState] of menu controlled by this area.
+ * @param enabled If false then gesture detector is disabled.
+ * @param content The content of the [ContextMenuArea].
+ */
+@Composable
+fun ContextMenuArea(
+    items: () -> List<ContextMenuItem>,
+    state: ContextMenuState = remember { ContextMenuState() },
+    enabled: Boolean = true,
+    content: @Composable () -> Unit
+) {
+    val data = ContextMenuData(items, LocalContextMenuData.current)
+
+    ContextMenuDataProvider(data) {
+        Box(Modifier.contextMenuDetector(state, enabled), propagateMinConstraints = true) {
+            content()
+        }
+        LocalContextMenuRepresentation.current.Representation(state, data.allItems)
+    }
+}
+
+/**
+ * Adds items to the hierarchy of context menu items. Can be used, for example, to customize
+ * context menu of text fields.
+ *
+ * @param items List of context menu items. Final context menu contains all items from descendant
+ * [ContextMenuArea] and [ContextMenuDataProvider].
+ * @param content The content of the [ContextMenuDataProvider].
+ *
+ * @see [[ContextMenuArea]]
+ */
+@Composable
+fun ContextMenuDataProvider(
+    items: () -> List<ContextMenuItem>,
+    content: @Composable () -> Unit
+) {
+    ContextMenuDataProvider(
+        ContextMenuData(items, LocalContextMenuData.current),
+        content
+    )
+}
+
+@Composable
+internal fun ContextMenuDataProvider(
+    data: ContextMenuData,
+    content: @Composable () -> Unit
+) {
+    CompositionLocalProvider(
+        LocalContextMenuData provides data
+    ) {
+        content()
+    }
+}
+
+private val LocalContextMenuData = staticCompositionLocalOf<ContextMenuData?> {
+    null
+}
+
+private fun Modifier.contextMenuDetector(
+    state: ContextMenuState,
+    enabled: Boolean = true
+): Modifier {
+    return if (
+        enabled && state.status == ContextMenuState.Status.Closed
+    ) {
+        this.pointerInput(state) {
+            awaitEachGesture {
+                val event = awaitEventFirstDown()
+                if (event.buttons.isSecondaryPressed) {
+                    event.changes.forEach { it.consume() }
+                    state.status =
+                        ContextMenuState.Status.Open(Rect(event.changes[0].position, 0f))
+                }
+            }
+        }
+    } else {
+        Modifier
+    }
+}
+
+private suspend fun AwaitPointerEventScope.awaitEventFirstDown(): PointerEvent {
+    var event: PointerEvent
+    do {
+        event = awaitPointerEvent()
+    } while (
+        !event.changes.fastAll { it.changedToDown() }
+    )
+    return event
+}
+
+/**
+ * Individual element of context menu.
+ *
+ * @param label The text to be displayed as a context menu item.
+ * @param onClick The action to be executed after click on the item.
+ */
+class ContextMenuItem(
+    val label: String,
+    val onClick: () -> Unit
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as ContextMenuItem
+
+        if (label != other.label) return false
+        if (onClick != other.onClick) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = label.hashCode()
+        result = 31 * result + onClick.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "ContextMenuItem(label='$label')"
+    }
+}
+
+/**
+ * Data container contains all [ContextMenuItem]s were defined previously in the hierarchy.
+ * [ContextMenuRepresentation] uses it to display context menu.
+ */
+class ContextMenuData(
+    val items: () -> List<ContextMenuItem>,
+    val next: ContextMenuData?
+) {
+
+    internal val allItems: List<ContextMenuItem> by lazy {
+        allItemsSeq.toList()
+    }
+
+    internal val allItemsSeq: Sequence<ContextMenuItem>
+        get() = sequence {
+            yieldAll(items())
+            next?.let { yieldAll(it.allItemsSeq) }
+        }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as ContextMenuData
+
+        if (items != other.items) return false
+        if (next != other.next) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = items.hashCode()
+        result = 31 * result + (next?.hashCode() ?: 0)
+        return result
+    }
+}
+
+/**
+ * Represents a state of context menu in [ContextMenuArea]. [status] is implemented
+ * via [androidx.compose.runtime.MutableState] so it's possible to track it inside @Composable
+ * functions.
+ */
+class ContextMenuState {
+    sealed class Status {
+        class Open(
+            val rect: Rect
+        ) : Status() {
+            override fun equals(other: Any?): Boolean {
+                if (this === other) return true
+                if (other == null || this::class != other::class) return false
+
+                other as Open
+
+                if (rect != other.rect) return false
+
+                return true
+            }
+
+            override fun hashCode(): Int {
+                return rect.hashCode()
+            }
+
+            override fun toString(): String {
+                return "Open(rect=$rect)"
+            }
+        }
+
+        object Closed : Status()
+    }
+
+    var status: Status by mutableStateOf(Status.Closed)
+}
+
+/**
+ * Implementations of this interface are responsible for displaying context menus. There are two
+ * implementations out of the box: [LightDefaultContextMenuRepresentation] and
+ * [DarkDefaultContextMenuRepresentation].
+ * To change currently used representation, different value for [LocalContextMenuRepresentation]
+ * could be provided.
+ */
+interface ContextMenuRepresentation {
+    @Composable
+    fun Representation(state: ContextMenuState, items: List<ContextMenuItem>)
+}
+
+/**
+ * Composition local that keeps [ContextMenuRepresentation] which is used by [ContextMenuArea]s.
+ */
+val LocalContextMenuRepresentation:
+    ProvidableCompositionLocal<ContextMenuRepresentation> = staticCompositionLocalOf {
+    LightDefaultContextMenuRepresentation
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/ContextMenu.desktop.kt b/compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/ContextMenu.desktop.kt
new file mode 100644
index 0000000..dcea9d1
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/ContextMenu.desktop.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.newtext.text.copypasta
+
+import androidx.compose.foundation.ContextMenuItem
+import androidx.compose.foundation.ContextMenuState
+import androidx.compose.foundation.newtext.text.copypasta.selection.SelectionManager
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.geometry.Offset
+import kotlinx.coroutines.flow.collect
+
+@Composable
+internal actual fun ContextMenuArea(
+    manager: SelectionManager,
+    content: @Composable () -> Unit
+) {
+    /* noop*/
+}
+
+@Composable
+internal fun OpenMenuAdjuster(state: ContextMenuState, adjustAction: (Offset) -> Unit) {
+    LaunchedEffect(state) {
+        snapshotFlow { state.status }.collect { status ->
+            if (status is ContextMenuState.Status.Open) {
+                adjustAction(status.rect.center)
+            }
+        }
+    }
+}
+
+@Composable
+internal fun SelectionManager.contextMenuItems(): () -> List<ContextMenuItem> {
+    return {
+        listOf()
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/StringHelpers.desktop.kt b/compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/StringHelpers.desktop.kt
new file mode 100644
index 0000000..58ac4c9
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/StringHelpers.desktop.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.newtext.text.copypasta
+
+import org.jetbrains.skia.BreakIterator
+
+internal actual fun String.findPrecedingBreak(index: Int): Int {
+    val it = BreakIterator.makeCharacterInstance()
+    it.setText(this)
+    return it.preceding(index)
+}
+
+internal actual fun String.findFollowingBreak(index: Int): Int {
+    val it = BreakIterator.makeCharacterInstance()
+    it.setText(this)
+    return it.following(index)
+}
\ No newline at end of file
diff --git a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt b/compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/TextPointerIcon.desktop.kt
similarity index 66%
copy from tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
copy to compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/TextPointerIcon.desktop.kt
index ebac64e..ff55d6d 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
+++ b/compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/TextPointerIcon.desktop.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package androidx.tv.material
+package androidx.compose.foundation.newtext.text.copypasta
 
-@RequiresOptIn(
-    "This tv-material API is experimental and likely to change or be removed in the future."
-)
-@Retention(AnnotationRetention.BINARY)
-annotation class ExperimentalTvMaterialApi
\ No newline at end of file
+import androidx.compose.ui.input.pointer.PointerIcon
+import java.awt.Cursor
+
+internal actual val textPointerIcon: PointerIcon =
+    PointerIcon(Cursor(Cursor.TEXT_CURSOR))
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/DesktopSelectionHandles.desktop.kt b/compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/DesktopSelectionHandles.desktop.kt
new file mode 100644
index 0000000..c509e01
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/DesktopSelectionHandles.desktop.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.newtext.text.copypasta.selection
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.text.style.ResolvedTextDirection
+
+@Composable
+internal actual fun SelectionHandle(
+    position: Offset,
+    isStartHandle: Boolean,
+    direction: ResolvedTextDirection,
+    handlesCrossed: Boolean,
+    modifier: Modifier,
+    content: (@Composable () -> Unit)?
+) {
+    // TODO
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionManager.desktop.kt b/compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionManager.desktop.kt
new file mode 100644
index 0000000..a03574a
--- /dev/null
+++ b/compose/foundation/foundation-newtext/src/desktopMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/selection/SelectionManager.desktop.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.newtext.text.copypasta.selection
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.key.KeyEvent
+
+// this doesn't sounds very sustainable
+// it would end up being a function for any conceptual keyevent (selectall, cut, copy, paste)
+// TODO(b/1564937)
+internal actual fun isCopyKeyEvent(keyEvent: KeyEvent) = true
+
+/**
+ * Magnification is not supported on desktop.
+ */
+internal actual fun Modifier.selectionMagnifier(manager: SelectionManager): Modifier = this
diff --git a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt b/compose/foundation/foundation-newtext/src/jvmMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/AtomicLong.kt
similarity index 67%
copy from tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
copy to compose/foundation/foundation-newtext/src/jvmMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/AtomicLong.kt
index ebac64e..8f2bd49 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
+++ b/compose/foundation/foundation-newtext/src/jvmMain/kotlin/androidx/compose/foundation/newtext/text/copypasta/AtomicLong.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * 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.
@@ -14,10 +14,8 @@
  * limitations under the License.
  */
 
-package androidx.tv.material
+// ktlint-disable filename
 
-@RequiresOptIn(
-    "This tv-material API is experimental and likely to change or be removed in the future."
-)
-@Retention(AnnotationRetention.BINARY)
-annotation class ExperimentalTvMaterialApi
\ No newline at end of file
+package androidx.compose.foundation.newtext.text.copypasta
+
+internal actual typealias AtomicLong = java.util.concurrent.atomic.AtomicLong
\ No newline at end of file
diff --git a/compose/foundation/foundation-newtext/src/test/kotlin/androidx/compose/foundation/newtext/text/modifiers/MultiParagraphLayoutCacheTest.kt b/compose/foundation/foundation-newtext/src/test/kotlin/androidx/compose/foundation/newtext/text/modifiers/MultiParagraphLayoutCacheTest.kt
index 40015b8..32ef628 100644
--- a/compose/foundation/foundation-newtext/src/test/kotlin/androidx/compose/foundation/newtext/text/modifiers/MultiParagraphLayoutCacheTest.kt
+++ b/compose/foundation/foundation-newtext/src/test/kotlin/androidx/compose/foundation/newtext/text/modifiers/MultiParagraphLayoutCacheTest.kt
@@ -33,13 +33,12 @@
     @Test(expected = IllegalStateException::class)
     fun whenMinInstrinsicWidth_withoutLayout_throws() {
         val textDelegate = MultiParagraphLayoutCache(
-            TextInlineContentLayoutDrawParams(
-                text = AnnotatedString(""),
-                style = TextStyle.Default,
-                fontFamilyResolver = fontFamilyResolver
-            ),
-            density = density,
-        )
+            text = AnnotatedString(""),
+            style = TextStyle.Default,
+            fontFamilyResolver = fontFamilyResolver
+        ).also {
+            it.density = density
+        }
 
         textDelegate.minIntrinsicWidth
     }
@@ -47,13 +46,12 @@
     @Test(expected = IllegalStateException::class)
     fun whenMaxIntrinsicWidth_withoutLayout_throws() {
         val textDelegate = MultiParagraphLayoutCache(
-            TextInlineContentLayoutDrawParams(
-                text = AnnotatedString(""),
-                style = TextStyle.Default,
-                fontFamilyResolver = fontFamilyResolver
-            ),
-            density = density,
-        )
+            text = AnnotatedString(""),
+            style = TextStyle.Default,
+            fontFamilyResolver = fontFamilyResolver
+        ).also {
+            it.density = density
+        }
 
         textDelegate.maxIntrinsicWidth
     }
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index dae8b69..1ccb795 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -81,7 +81,7 @@
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable public static void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int filterQuality);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable public static void Image(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
     method @androidx.compose.runtime.Composable public static void Image(androidx.compose.ui.graphics.painter.Painter painter, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
-    method @Deprecated @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable public static void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, String contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
+    method @Deprecated @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable public static void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
   }
 
   @androidx.compose.runtime.Stable public interface Indication {
@@ -1014,6 +1014,9 @@
   public final class LongPressTextDragObserverKt {
   }
 
+  public final class PointerMoveDetectorKt {
+  }
+
   public final class StringHelpersKt {
   }
 
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index 681c092..c01d3ec 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -99,7 +99,7 @@
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable public static void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int filterQuality);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable public static void Image(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
     method @androidx.compose.runtime.Composable public static void Image(androidx.compose.ui.graphics.painter.Painter painter, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
-    method @Deprecated @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable public static void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, String contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
+    method @Deprecated @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable public static void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
   }
 
   @androidx.compose.runtime.Stable public interface Indication {
@@ -902,10 +902,10 @@
   public final class LazyStaggeredGridDslKt {
     method @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void LazyHorizontalStaggeredGrid(androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells rows, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope,kotlin.Unit> content);
     method @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void LazyVerticalStaggeredGrid(androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells columns, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope,kotlin.Unit> content);
-    method @androidx.compose.foundation.ExperimentalFoundationApi public static <T> void items(androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, optional kotlin.jvm.functions.Function1<? super T,?> contentType, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope,? super T,kotlin.Unit> itemContent);
-    method @androidx.compose.foundation.ExperimentalFoundationApi public static <T> void items(androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope, T![] items, optional kotlin.jvm.functions.Function1<? super T,?>? key, optional kotlin.jvm.functions.Function1<? super T,?> contentType, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope,? super T,kotlin.Unit> itemContent);
-    method @androidx.compose.foundation.ExperimentalFoundationApi public static <T> void itemsIndexed(androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?> contentType, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
-    method @androidx.compose.foundation.ExperimentalFoundationApi public static <T> void itemsIndexed(androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope, T![] items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?> contentType, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method @androidx.compose.foundation.ExperimentalFoundationApi public static inline <T> void items(androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, optional kotlin.jvm.functions.Function1<? super T,?> contentType, optional kotlin.jvm.functions.Function1<? super T,androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan>? span, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope,? super T,kotlin.Unit> itemContent);
+    method @androidx.compose.foundation.ExperimentalFoundationApi public static inline <T> void items(androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope, T![] items, optional kotlin.jvm.functions.Function1<? super T,?>? key, optional kotlin.jvm.functions.Function1<? super T,?> contentType, optional kotlin.jvm.functions.Function1<? super T,androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan>? span, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope,? super T,kotlin.Unit> itemContent);
+    method @androidx.compose.foundation.ExperimentalFoundationApi public static inline <T> void itemsIndexed(androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?> contentType, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan>? span, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method @androidx.compose.foundation.ExperimentalFoundationApi public static inline <T> void itemsIndexed(androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope, T![] items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?> contentType, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan>? span, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
   }
 
   @androidx.compose.foundation.ExperimentalFoundationApi public sealed interface LazyStaggeredGridItemInfo {
@@ -959,8 +959,8 @@
   }
 
   @androidx.compose.foundation.ExperimentalFoundationApi public sealed interface LazyStaggeredGridScope {
-    method @androidx.compose.foundation.ExperimentalFoundationApi public void item(optional Object? key, optional Object? contentType, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope,kotlin.Unit> content);
-    method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?> contentType, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
+    method @androidx.compose.foundation.ExperimentalFoundationApi public void item(optional Object? key, optional Object? contentType, optional androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan? span, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope,kotlin.Unit> content);
+    method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?> contentType, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan>? span, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
   }
 
   public final class LazyStaggeredGridSemanticsKt {
@@ -1010,6 +1010,17 @@
     method public java.util.List<java.lang.Integer> calculateCrossAxisCellSizes(androidx.compose.ui.unit.Density, int availableSize, int spacing);
   }
 
+  @androidx.compose.foundation.ExperimentalFoundationApi public final class StaggeredGridItemSpan {
+    field public static final androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan.Companion Companion;
+  }
+
+  public static final class StaggeredGridItemSpan.Companion {
+    method public androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan getFullLine();
+    method public androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan getSingleLane();
+    property public final androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan FullLine;
+    property public final androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan SingleLane;
+  }
+
 }
 
 package androidx.compose.foundation.pager {
@@ -1260,6 +1271,7 @@
 
   public final class ClickableTextKt {
     method @androidx.compose.runtime.Composable public static void ClickableText(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional boolean softWrap, optional int overflow, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> onClick);
+    method @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void ClickableText(androidx.compose.ui.text.AnnotatedString text, kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> onHover, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.text.TextStyle style, optional boolean softWrap, optional int overflow, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> onClick);
   }
 
   public final class ContextMenu_androidKt {
@@ -1350,6 +1362,9 @@
   public final class LongPressTextDragObserverKt {
   }
 
+  public final class PointerMoveDetectorKt {
+  }
+
   public final class StringHelpersKt {
   }
 
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index dae8b69..1ccb795 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -81,7 +81,7 @@
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable public static void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter, optional int filterQuality);
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable public static void Image(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
     method @androidx.compose.runtime.Composable public static void Image(androidx.compose.ui.graphics.painter.Painter painter, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
-    method @Deprecated @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable public static void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, String contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
+    method @Deprecated @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable public static void Image(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment alignment, optional androidx.compose.ui.layout.ContentScale contentScale, optional float alpha, optional androidx.compose.ui.graphics.ColorFilter? colorFilter);
   }
 
   @androidx.compose.runtime.Stable public interface Indication {
@@ -1014,6 +1014,9 @@
   public final class LongPressTextDragObserverKt {
   }
 
+  public final class PointerMoveDetectorKt {
+  }
+
   public final class StringHelpersKt {
   }
 
diff --git a/compose/foundation/foundation/benchmark/build.gradle b/compose/foundation/foundation/benchmark/build.gradle
index 2b61744..905f5b4 100644
--- a/compose/foundation/foundation/benchmark/build.gradle
+++ b/compose/foundation/foundation/benchmark/build.gradle
@@ -31,6 +31,7 @@
     androidTestImplementation project(":compose:ui:ui-text:ui-text-benchmark")
     androidTestImplementation project(":compose:foundation:foundation-layout")
     androidTestImplementation project(":compose:foundation:foundation")
+    androidTestImplementation project(":compose:foundation:foundation-do-not-ship-newtext")
     androidTestImplementation project(":compose:material:material")
     androidTestImplementation project(":compose:benchmark-utils")
     androidTestImplementation(libs.testRules)
@@ -43,6 +44,10 @@
 
 android {
     namespace "androidx.compose.foundation.benchmark"
+    defaultConfig {
+        // must be one of: 'None', 'StackSampling', or 'MethodTracing'
+        testInstrumentationRunnerArguments["androidx.benchmark.profiling.mode"]= 'None'
+    }
 }
 
 androidx {
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/empirical/IfNotEmptyCallText.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/empirical/IfNotEmptyCallText.kt
index 650c032..e19ae6b 100644
--- a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/empirical/IfNotEmptyCallText.kt
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/empirical/IfNotEmptyCallText.kt
@@ -16,7 +16,7 @@
 
 package androidx.compose.foundation.benchmark.text.empirical
 
-import androidx.compose.material.Text
+import androidx.compose.foundation.text.BasicText
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.testutils.LayeredComposeTestCase
@@ -24,6 +24,7 @@
 import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
 import androidx.compose.testutils.benchmark.toggleStateBenchmarkComposeMeasureLayout
 import androidx.compose.testutils.benchmark.toggleStateBenchmarkRecompose
+import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontFamily
 import androidx.test.filters.LargeTest
 import org.junit.Rule
@@ -39,10 +40,15 @@
 class IfNotEmptyCallText(private val text: String) : LayeredComposeTestCase(), ToggleableTestCase {
     private var toggleText = mutableStateOf("")
 
+    private val style = TextStyle.Default.copy(fontFamily = FontFamily.Monospace)
+
     @Composable
     override fun MeasuredContent() {
         if (toggleText.value.isNotEmpty()) {
-            Text(toggleText.value, fontFamily = FontFamily.Monospace)
+            BasicText(
+                toggleText.value,
+                style = style
+            )
         }
     }
 
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/empirical/ModifierIfNotEmptyCallText.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/empirical/ModifierIfNotEmptyCallText.kt
new file mode 100644
index 0000000..18424f4
--- /dev/null
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/empirical/ModifierIfNotEmptyCallText.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.compose.foundation.benchmark.text.empirical
+
+import androidx.compose.foundation.newtext.text.TextUsingModifier
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.ToggleableTestCase
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkComposeMeasureLayout
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkRecompose
+import androidx.compose.ui.text.ExperimentalTextApi
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.test.filters.LargeTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Toggle between missing Text and Text("aaa..") to simulate backend text loading.
+ *
+ * This intentionally hits as many text caches as possible, to isolate compose setText behavior.
+ */
+@OptIn(ExperimentalTextApi::class)
+class ModifierIfNotEmptyCallText(
+    private val text: String
+) : LayeredComposeTestCase(), ToggleableTestCase {
+    private var toggleText = mutableStateOf("")
+
+    private val style = TextStyle.Default.copy(fontFamily = FontFamily.Monospace)
+
+    @Composable
+    override fun MeasuredContent() {
+        if (toggleText.value.isNotEmpty()) {
+            TextUsingModifier(
+                text = toggleText.value,
+                style = style
+            )
+        }
+    }
+
+    override fun toggleState() {
+        if (toggleText.value == "") {
+            toggleText.value = text
+        } else {
+            toggleText.value = ""
+        }
+    }
+}
+
+@LargeTest
+@RunWith(Parameterized::class)
+open class ModifierIfNotEmptyParent(private val size: Int) {
+
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    private val caseFactory = {
+        val text = generateCacheableStringOf(size)
+        ModifierIfNotEmptyCallText(text)
+    }
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "size={0}")
+        fun initParameters(): Array<Any> = arrayOf()
+    }
+
+    @Test
+    fun recomposeOnly() {
+        benchmarkRule.toggleStateBenchmarkRecompose(caseFactory)
+    }
+
+    @Test
+    fun recomposeMeasureLayout() {
+        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(caseFactory)
+    }
+}
+
+/**
+ * Metrics determined from all apps
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+class ModifierAllAppsIfNotEmptyCallText(size: Int) : ModifierIfNotEmptyParent(size) {
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "size={0}")
+        fun initParameters(): Array<Any> = AllApps.TextLengths
+    }
+}
+
+/**
+ * Metrics for Chat-like apps.
+ *
+ * These apps typically have more longer strings, due to user generated content.
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+class ModifierChatAppIfNotEmptyCallText(size: Int) : ModifierIfNotEmptyParent(size) {
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "size={0}")
+        fun initParameters(): Array<Any> = ChatApps.TextLengths
+    }
+}
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/empirical/ModifierSetText.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/empirical/ModifierSetText.kt
new file mode 100644
index 0000000..e460323
--- /dev/null
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/empirical/ModifierSetText.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.compose.foundation.benchmark.text.empirical
+
+import androidx.compose.foundation.newtext.text.TextUsingModifier
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.ToggleableTestCase
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkComposeMeasureLayout
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkRecompose
+import androidx.compose.ui.text.ExperimentalTextApi
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.test.filters.LargeTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Toggle between "" and "aaaa..." to simulate backend text loading.
+ *
+ * This intentionally hits as many text caches as possible, to isolate compose setText behavior.
+ */
+class ModifierSetText(private val text: String) : LayeredComposeTestCase(), ToggleableTestCase {
+    private val toggleText = mutableStateOf("")
+
+    private val style = TextStyle.Default.copy(fontFamily = FontFamily.Monospace)
+
+    @OptIn(ExperimentalTextApi::class)
+    @Composable
+    override fun MeasuredContent() {
+        TextUsingModifier(
+            toggleText.value,
+            style = style
+        )
+    }
+
+    override fun toggleState() {
+        if (toggleText.value == "") {
+            toggleText.value = text
+        } else {
+            toggleText.value = ""
+        }
+    }
+}
+
+@LargeTest
+@RunWith(Parameterized::class)
+open class ModifierSetTextParent(private val size: Int) {
+
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    private val caseFactory = {
+        val text = generateCacheableStringOf(size)
+        ModifierSetText(text)
+    }
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "size={0}")
+        fun initParameters(): Array<Any> = arrayOf()
+    }
+
+    @Test
+    fun recomposeOnly() {
+        benchmarkRule.toggleStateBenchmarkRecompose(caseFactory)
+    }
+
+    @Test
+    fun recomposeMeasureLayout() {
+        benchmarkRule.toggleStateBenchmarkComposeMeasureLayout(caseFactory)
+    }
+}
+
+/**
+ * Metrics determined from all apps
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+class ModifierAllAppsSetText(size: Int) : ModifierSetTextParent(size) {
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "size={0}")
+        fun initParameters(): Array<Any> = AllApps.TextLengths
+    }
+}
+
+/**
+ * Metrics for Chat-like apps.
+ *
+ * These apps typically have more longer strings, due to user generated content.
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+class ModifierChatAppSetText(size: Int) : ModifierSetTextParent(size) {
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "size={0}")
+        fun initParameters(): Array<Any> = ChatApps.TextLengths
+    }
+}
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/empirical/SetText.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/empirical/SetText.kt
index 36cf78a..b82eb69 100644
--- a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/empirical/SetText.kt
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/text/empirical/SetText.kt
@@ -16,7 +16,7 @@
 
 package androidx.compose.foundation.benchmark.text.empirical
 
-import androidx.compose.material.Text
+import androidx.compose.foundation.text.BasicText
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.testutils.LayeredComposeTestCase
@@ -24,6 +24,7 @@
 import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
 import androidx.compose.testutils.benchmark.toggleStateBenchmarkComposeMeasureLayout
 import androidx.compose.testutils.benchmark.toggleStateBenchmarkRecompose
+import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontFamily
 import androidx.test.filters.LargeTest
 import org.junit.Rule
@@ -39,9 +40,14 @@
 class SetText(private val text: String) : LayeredComposeTestCase(), ToggleableTestCase {
     private var toggleText = mutableStateOf("")
 
+    private val style = TextStyle.Default.copy(fontFamily = FontFamily.Monospace)
+
     @Composable
     override fun MeasuredContent() {
-        Text(toggleText.value, fontFamily = FontFamily.Monospace)
+        BasicText(
+            toggleText.value,
+            style = style
+        )
     }
 
     override fun toggleState() {
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
index 8972e35..7092564 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
@@ -59,6 +59,7 @@
 import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
 import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
+import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
 import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.samples.StickyHeaderSample
@@ -972,18 +973,26 @@
             Button(onClick = { if (count != 0) count-- }) { Text(text = "--") }
         }
 
-        val state = rememberLazyStaggeredGridState()
+        val state = rememberLazyStaggeredGridState(initialFirstVisibleItemIndex = 29)
 
         LazyVerticalStaggeredGrid(
             columns = StaggeredGridCells.Fixed(3),
             modifier = Modifier.fillMaxSize(),
             state = state,
-            contentPadding = PaddingValues(vertical = 500.dp, horizontal = 20.dp),
+            contentPadding = PaddingValues(vertical = 30.dp, horizontal = 20.dp),
             horizontalArrangement = Arrangement.spacedBy(10.dp),
             verticalArrangement = Arrangement.spacedBy(10.dp),
             content = {
-                items(count) {
-                    var expanded by rememberSaveable { mutableStateOf(false) }
+                items(
+                    count,
+                    span = {
+                        if (it % 30 == 0)
+                            StaggeredGridItemSpan.FullLine
+                        else
+                            StaggeredGridItemSpan.SingleLane
+                    }
+                ) {
+                    var expanded by remember { mutableStateOf(false) }
                     val index = indices.value[it % indices.value.size]
                     val color = colors[index]
                     Box(
@@ -1059,7 +1068,10 @@
                     Checkbox(checked = selected, onCheckedChange = {
                         selectedIndexes[item] = it
                     })
-                    Spacer(Modifier.width(16.dp).height(height))
+                    Spacer(
+                        Modifier
+                            .width(16.dp)
+                            .height(height))
                     Text("Item $item")
                 }
             }
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/InteractiveText.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/InteractiveText.kt
index a730aef..b671f6b 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/InteractiveText.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/InteractiveText.kt
@@ -16,31 +16,59 @@
 
 package androidx.compose.foundation.demos.text
 
-import androidx.compose.foundation.text.ClickableText
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.border
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.text.ClickableText
+import androidx.compose.material.Button
+import androidx.compose.material.MaterialTheme
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
 
-@Preview
+@OptIn(ExperimentalFoundationApi::class)
 @Composable
 fun InteractiveTextDemo() {
-    TextOnClick()
-}
+    val clickedOffset = remember { mutableStateOf<Int?>(null) }
+    val hoveredOffset = remember { mutableStateOf<Int?>(null) }
+    val numOnHoverInvocations = remember { mutableStateOf(0) }
+    Column(
+        modifier = Modifier.padding(horizontal = 10.dp)
+    ) {
+        Text(text = "ClickableText onHover", style = MaterialTheme.typography.h6)
 
-@Preview
-@Composable
-fun TextOnClick() {
-    val clickedOffset = remember { mutableStateOf(-1) }
-    Column {
-        Text("Clicked Offset: ${clickedOffset.value}")
+        Text(text = "Click/Hover the lorem ipsum text below.")
+        Text(text = "Clicked offset: ${clickedOffset.value ?: "No click yet"}")
+        Text(text = "Hovered offset: ${hoveredOffset.value ?: "Not hovering"}")
+        Text(text = "Number of onHover invocations: ${numOnHoverInvocations.value}")
+
         ClickableText(
-            text = AnnotatedString("Click Me")
+            text = AnnotatedString(loremIpsum(wordCount = 30)),
+            modifier = Modifier.border(Dp.Hairline, Color.Black),
+            style = MaterialTheme.typography.body1,
+            onHover = {
+                numOnHoverInvocations.value = numOnHoverInvocations.value + 1
+                hoveredOffset.value = it
+            }
         ) { offset ->
             clickedOffset.value = offset
         }
+
+        Button(
+            onClick = {
+                clickedOffset.value = null
+                hoveredOffset.value = null
+                numOnHoverInvocations.value = 0
+            }
+        ) {
+            Text(text = "Reset Offsets/Counter")
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/MemoryAllocs.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/MemoryAllocs.kt
new file mode 100644
index 0000000..bfbb956
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/MemoryAllocs.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.demos.text
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.produceState
+import androidx.compose.runtime.withFrameMillis
+
+/**
+ * These demos are for using the memory profiler to observe initial compo and recompo memory
+ * pressure.
+ *
+ * Emulate recompose when string loads
+ */
+@Composable
+fun MemoryAllocsSetText() {
+    Column {
+        Text("Run in memory profiler to emulate setting text value when observable loads")
+        Text("This is designed to be used in the Android Studio memory profiler")
+        SetText(textToggler())
+    }
+}
+
+/**
+ * These demos are for using the memory profiler to observe initial compo and recompo memory
+ * pressure.
+ *
+ * Emulate calling text when string loads
+ */
+@Composable
+fun MemoryAllocsIfNotEmptyText() {
+    Column {
+        Text("Run in memory profiler to emulate calling Text after an observable loads")
+        Text("This is designed to be used in the Android Studio memory profiler")
+        IfNotEmptyText(textToggler())
+    }
+}
+
+@Composable
+fun IfNotEmptyText(text: State<String>) {
+    if (text.value.isNotEmpty()) {
+        Text(text.value)
+    }
+}
+
+@Composable
+private fun SetText(text: State<String>) {
+    Text(text.value)
+}
+
+@Composable
+private fun textToggler(): State<String> = produceState("") {
+    while (true) {
+        withFrameMillis {
+            value = if (value.isEmpty()) {
+                "This text and empty string swap every frame"
+            } else {
+                ""
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
index 7f26faf..62e4dbf 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
@@ -115,6 +115,13 @@
                 ComposableDemo("Text selection") { TextSelectionDemo() },
                 ComposableDemo("Text selection sample") { TextSelectionSample() },
             )
+        ),
+        DemoCategory(
+            "⚠️️ Memory benchmark ⚠️️",
+            listOf(
+                ComposableDemo("SetText") { MemoryAllocsSetText() },
+                ComposableDemo("IfNotEmptyText") { MemoryAllocsIfNotEmptyText() }
+            )
         )
     )
 )
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/TransformableSample.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/TransformableSample.kt
index 51d0537..b0f117c 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/TransformableSample.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/TransformableSample.kt
@@ -44,14 +44,18 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
-import kotlinx.coroutines.launch
+import kotlin.math.max
 import kotlin.math.roundToInt
+import kotlinx.coroutines.launch
 
 @Sampled
 @Composable
 fun TransformableSample() {
     Box(
-        Modifier.size(200.dp).clipToBounds().background(Color.LightGray)
+        Modifier
+            .size(200.dp)
+            .clipToBounds()
+            .background(Color.LightGray)
     ) {
         // set up all transformation states
         var scale by remember { mutableStateOf(1f) }
@@ -61,7 +65,8 @@
         // let's create a modifier state to specify how to update our UI state defined above
         val state = rememberTransformableState { zoomChange, offsetChange, rotationChange ->
             // note: scale goes by factor, not an absolute difference, so we need to multiply it
-            scale *= zoomChange
+            // for this example, we don't allow downscaling, so cap it to 1f
+            scale = max(scale * zoomChange, 1f)
             rotation += rotationChange
             offset += offsetChange
         }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/BaseLazyLayoutTestWithOrientation.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/BaseLazyLayoutTestWithOrientation.kt
index e1837d2..7a60556 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/BaseLazyLayoutTestWithOrientation.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/BaseLazyLayoutTestWithOrientation.kt
@@ -33,6 +33,8 @@
 import androidx.compose.ui.test.getUnclippedBoundsInRoot
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpOffset
+import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import org.junit.Rule
 
@@ -119,6 +121,15 @@
             assertTopPositionInRootIsEqualTo(expectedStart)
         }
 
+    fun SemanticsNodeInteraction.assertAxisBounds(
+        offset: DpOffset,
+        size: DpSize
+    ) =
+        assertMainAxisStartPositionInRootIsEqualTo(offset.y)
+            .assertCrossAxisStartPositionInRootIsEqualTo(offset.x)
+            .assertMainAxisSizeIsEqualTo(size.height)
+            .assertCrossAxisSizeIsEqualTo(size.width)
+
     fun PaddingValues(
         mainAxis: Dp = 0.dp,
         crossAxis: Dp = 0.dp
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/BasicMarqueeTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/BasicMarqueeTest.kt
index 5992475..8f5638a 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/BasicMarqueeTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/BasicMarqueeTest.kt
@@ -25,6 +25,7 @@
 import androidx.compose.foundation.layout.width
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
@@ -32,6 +33,7 @@
 import androidx.compose.testutils.assertDoesNotContainColor
 import androidx.compose.testutils.assertPixels
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.MotionDurationScale
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.focus.FocusManager
 import androidx.compose.ui.focus.FocusRequester
@@ -50,10 +52,10 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
-import androidx.testutils.AnimationDurationScaleRule
 import com.google.common.truth.Truth.assertThat
 import kotlin.math.roundToInt
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -74,10 +76,6 @@
     @get:Rule
     val rule = createComposeRule()
 
-    @get:Rule
-    val animationScaleRule: AnimationDurationScaleRule =
-        AnimationDurationScaleRule.createForAllTests(1f)
-
     /**
      * Converts pxPerFrame to dps per second. The frame delay is 16ms, which means there are
      * actually slightly more than 60 frames in a second (62.5).
@@ -103,7 +101,7 @@
     }
 
     @Test
-    fun animationDisabled() {
+    fun doesNotAnimate_whenZeroIterations() {
         rule.setContent {
             TestMarqueeContent(
                 Modifier.basicMarqueeWithTestParams(
@@ -121,6 +119,65 @@
         }
     }
 
+    @Ignore("b/265177763: Not currently possible to inject a MotionDurationScale in tests.")
+    @Test fun animates_whenAnimationsDisabledBySystem() {
+        // TODO(b/265177763) Inject 0 duration scale.
+
+        rule.setContent {
+            LaunchedEffect(Unit) {
+                assertThat(coroutineContext[MotionDurationScale]?.scaleFactor).isEqualTo(0f)
+            }
+            TestMarqueeContent(
+                Modifier.basicMarqueeWithTestParams(
+                    iterations = 1,
+                    spacing = MarqueeSpacing(0.toDp())
+                )
+            )
+        }
+
+        rule.onRoot().captureToImage()
+            .assertPixels(expectedSize = IntSize(100, 100)) { Color1 }
+
+        // First stage of animation: show all the content.
+        repeat(30) { frameNum ->
+            rule.mainClock.advanceTimeByFrame()
+            rule.waitForIdle()
+            val image = rule.onRoot().captureToImage()
+            val edge1 = image.findFirstColorEdge(Color1, Color2)
+            val edge2 = image.findFirstColorEdge(Color2, Color1)
+            val expectedEdge1 = 100 - (frameNum * 10)
+            val expectedEdge2 = 100 - ((frameNum - 10) * 10)
+
+            when {
+                frameNum == 0 -> {
+                    assertThat(edge1).isEqualTo(-1)
+                    assertThat(edge2).isEqualTo(-1)
+                }
+
+                frameNum < 10 -> {
+                    assertThat(edge1).isEqualTo(expectedEdge1)
+                    assertThat(edge2).isEqualTo(-1)
+                }
+
+                frameNum == 10 -> {
+                    assertThat(edge1).isEqualTo(-1)
+                    assertThat(edge2).isEqualTo(-1)
+                }
+
+                frameNum < 20 -> {
+                    assertThat(edge1).isEqualTo(-1)
+                    assertThat(edge2).isEqualTo(expectedEdge2)
+                }
+
+                else -> {
+                    // Nothing should happen after the animation finishes.
+                    assertThat(edge1).isEqualTo(-1)
+                    assertThat(edge2).isEqualTo(-1)
+                }
+            }
+        }
+    }
+
     @Test
     fun animates_singleIteration_noSpace() {
         rule.setContent {
@@ -1076,7 +1133,6 @@
                 edgeStartX = x
             } else if (pixel == right) {
                 return if (edgeStartX >= 0) {
-                    println("OMG found edge: $edgeStartX - $x")
                     ((edgeStartX.toFloat() + x.toFloat()) / 2f).roundToInt()
                 } else {
                     // Never found the start of the edge.
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ClickableInScrollableViewGroupTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ClickableInScrollableViewGroupTest.kt
index 0ceccea..eb18a04 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ClickableInScrollableViewGroupTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ClickableInScrollableViewGroupTest.kt
@@ -37,7 +37,9 @@
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.launch
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -203,6 +205,10 @@
     /**
      * Test case for a [clickable] inside an [AndroidView] inside a non-scrollable Compose container
      */
+    @Ignore(
+        "b/203141462 - currently this is not implemented so AndroidView()s will always " +
+            "appear scrollable"
+    )
     @Test
     fun clickable_androidViewInNotScrollableContainer() {
         val interactionSource = MutableInteractionSource()
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
index d182639..5baa306 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
@@ -22,6 +22,7 @@
 import androidx.compose.animation.rememberSplineBasedDecay
 import androidx.compose.foundation.gestures.DefaultFlingBehavior
 import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ModifierLocalScrollableContainer
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.foundation.gestures.ScrollableDefaults
@@ -59,7 +60,6 @@
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.consumeScrollContainerInfo
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
@@ -69,6 +69,8 @@
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.input.pointer.util.VelocityTracker
 import androidx.compose.ui.materialize
+import androidx.compose.ui.modifier.ModifierLocalConsumer
+import androidx.compose.ui.modifier.ModifierLocalReadScope
 import androidx.compose.ui.platform.AbstractComposeView
 import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.platform.LocalFocusManager
@@ -1688,73 +1690,49 @@
     fun scrollable_setsModifierLocalScrollableContainer() {
         val controller = ScrollableState { it }
 
-        var isOuterInVerticalScrollableContainer: Boolean? = null
-        var isInnerInVerticalScrollableContainer: Boolean? = null
-        var isOuterInHorizontalScrollableContainer: Boolean? = null
-        var isInnerInHorizontalScrollableContainer: Boolean? = null
+        var isOuterInScrollableContainer: Boolean? = null
+        var isInnerInScrollableContainer: Boolean? = null
         rule.setContent {
             Box {
                 Box(
                     modifier = Modifier
                         .testTag(scrollableBoxTag)
                         .size(100.dp)
-                        .consumeScrollContainerInfo {
-                            isOuterInVerticalScrollableContainer = it?.canScrollVertically()
-                            isOuterInHorizontalScrollableContainer = it?.canScrollHorizontally()
-                        }
+                        .then(
+                            object : ModifierLocalConsumer {
+                                override fun onModifierLocalsUpdated(
+                                    scope: ModifierLocalReadScope
+                                ) {
+                                    with(scope) {
+                                        isOuterInScrollableContainer =
+                                            ModifierLocalScrollableContainer.current
+                                    }
+                                }
+                            }
+                        )
                         .scrollable(
                             state = controller,
                             orientation = Orientation.Horizontal
                         )
-                        .consumeScrollContainerInfo {
-                            isInnerInHorizontalScrollableContainer = it?.canScrollHorizontally()
-                            isInnerInVerticalScrollableContainer = it?.canScrollVertically()
-                        }
+                        .then(
+                            object : ModifierLocalConsumer {
+                                override fun onModifierLocalsUpdated(
+                                    scope: ModifierLocalReadScope
+                                ) {
+                                    with(scope) {
+                                        isInnerInScrollableContainer =
+                                            ModifierLocalScrollableContainer.current
+                                    }
+                                }
+                            }
+                        )
                 )
             }
         }
 
         rule.runOnIdle {
-            assertThat(isInnerInHorizontalScrollableContainer).isTrue()
-            assertThat(isInnerInVerticalScrollableContainer).isFalse()
-            assertThat(isOuterInVerticalScrollableContainer).isFalse()
-            assertThat(isOuterInHorizontalScrollableContainer).isFalse()
-        }
-    }
-
-    @Test
-    fun scrollable_nested_setsModifierLocalScrollableContainer() {
-        val horizontalController = ScrollableState { it }
-        val verticalController = ScrollableState { it }
-
-        var horizontalDrag: Boolean? = null
-        var verticalDrag: Boolean? = null
-        rule.setContent {
-            Box(
-                modifier = Modifier
-                    .size(100.dp)
-                    .scrollable(
-                        state = horizontalController,
-                        orientation = Orientation.Horizontal
-                    )
-            ) {
-                Box(
-                    modifier = Modifier
-                        .size(100.dp)
-                        .scrollable(
-                            state = verticalController,
-                            orientation = Orientation.Vertical
-                        )
-                        .consumeScrollContainerInfo {
-                            horizontalDrag = it?.canScrollHorizontally()
-                            verticalDrag = it?.canScrollVertically()
-                        })
-            }
-        }
-
-        rule.runOnIdle {
-            assertThat(horizontalDrag).isTrue()
-            assertThat(verticalDrag).isTrue()
+            assertThat(isOuterInScrollableContainer).isFalse()
+            assertThat(isInnerInScrollableContainer).isTrue()
         }
     }
 
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TransformableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TransformableTest.kt
index 3fb977b..2713153 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TransformableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/TransformableTest.kt
@@ -159,6 +159,35 @@
     }
 
     @Test
+    fun transformable_panWithOneFinger() {
+        var cumulativePan = Offset.Zero
+        var touchSlop = 0f
+
+        setTransformableContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            Modifier.transformable(
+                state = rememberTransformableState { _, pan, _ ->
+                    cumulativePan += pan
+                }
+            )
+        }
+
+        val expected = Offset(50f + touchSlop, 0f)
+
+        rule.onNodeWithTag(TEST_TAG).performTouchInput {
+            down(1, center)
+            moveBy(1, expected)
+            up(1)
+        }
+
+        rule.mainClock.advanceTimeBy(milliseconds = 1000)
+
+        rule.runOnIdle {
+            assertWithMessage("Should have panned 20/10").that(cumulativePan).isEqualTo(expected)
+        }
+    }
+
+    @Test
     fun transformable_rotate() {
         var cumulativeRotation = 0f
 
@@ -290,8 +319,10 @@
         val state = TransformableState { zoom, _, _ ->
             cumulativeScale *= zoom
         }
+        var slop: Float = 0f
 
         setTransformableContent {
+            slop = LocalViewConfiguration.current.touchSlop
             Modifier.transformable(state = state)
         }
 
@@ -302,7 +333,7 @@
         rule.onNodeWithTag(TEST_TAG).performTouchInput {
             down(pointerId = 1, center)
             down(pointerId = 2, center + Offset(10f, 10f))
-            moveBy(2, Offset(20f, 20f))
+            moveBy(2, Offset(slop * 2, slop * 2))
         }
 
         assertThat(state.isTransformInProgress).isEqualTo(true)
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimatedScrollTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimatedScrollTest.kt
index 7f7e5da..dbb0f0c 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimatedScrollTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridAnimatedScrollTest.kt
@@ -19,20 +19,15 @@
 import androidx.compose.animation.core.FloatSpringSpec
 import androidx.compose.foundation.AutoTestFrameClock
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.border
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.animateScrollBy
 import androidx.compose.foundation.lazy.grid.isEqualTo
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
 import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
@@ -139,7 +134,32 @@
             state.animateScrollToItem(100)
         }
         rule.waitForIdle()
-        assertThat(state.firstVisibleItemIndex).isEqualTo(90)
+        assertThat(state.firstVisibleItemIndex).isEqualTo(91)
+        assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
+    }
+
+    @Test
+    fun animateScrollToItem_toFullSpan() {
+        runBlocking(Dispatchers.Main + AutoTestFrameClock()) {
+            state.animateScrollToItem(50, 10)
+        }
+        rule.waitForIdle()
+        assertThat(state.firstVisibleItemIndex).isEqualTo(50)
+        assertThat(state.firstVisibleItemScrollOffset).isEqualTo(10)
+    }
+
+    @Test
+    fun animateScrollToItem_toFullSpan_andBack() {
+        runBlocking(Dispatchers.Main + AutoTestFrameClock()) {
+            state.animateScrollToItem(50, 10)
+        }
+        rule.waitForIdle()
+
+        runBlocking(Dispatchers.Main + AutoTestFrameClock()) {
+            state.animateScrollToItem(45, 0)
+        }
+
+        assertThat(state.firstVisibleItemIndex).isEqualTo(44)
         assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
     }
 
@@ -232,13 +252,22 @@
             state = state,
             modifier = Modifier.axisSize(itemSizeDp * 2, itemSizeDp * 5)
         ) {
-            items(100) {
+            items(
+                count = 100,
+                span = {
+                    // mark a span to check scroll through
+                    if (it == 50)
+                        StaggeredGridItemSpan.FullLine
+                    else
+                        StaggeredGridItemSpan.SingleLane
+                }
+            ) {
                 BasicText(
                     "$it",
                     Modifier
                         .mainAxisSize(itemSizeDp)
                         .testTag("$it")
-                        .border(1.dp, Color.Black)
+                        .debugBorder()
                 )
             }
         }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridContentPaddingTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridContentPaddingTest.kt
index c718b43..a17afd8 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridContentPaddingTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridContentPaddingTest.kt
@@ -121,7 +121,7 @@
                 state = state
             ) {
                 items(100) {
-                    Spacer(Modifier.mainAxisSize(itemSizeDp).testTag("$it"))
+                    Spacer(Modifier.mainAxisSize(itemSizeDp).testTag("$it").debugBorder())
                 }
             }
         }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLaneInfoTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLaneInfoTest.kt
new file mode 100644
index 0000000..aac67a3
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLaneInfoTest.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.compose.foundation.lazy.staggeredgrid
+
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertEquals
+import org.junit.Test
+
+class LazyStaggeredGridLaneInfoTest {
+    private val laneInfo = LazyStaggeredGridLaneInfo()
+
+    @Test
+    fun emptySpan_unset() {
+        assertEquals(LazyStaggeredGridLaneInfo.Unset, laneInfo.getLane(0))
+    }
+
+    @Test
+    fun setLane() {
+        laneInfo.setLane(0, 42)
+        laneInfo.setLane(1, 0)
+
+        assertEquals(42, laneInfo.getLane(0))
+        assertEquals(0, laneInfo.getLane(1))
+    }
+
+    @Test
+    fun setLane_beyondBound() {
+        val bound = laneInfo.upperBound()
+        laneInfo.setLane(bound - 1, 42)
+        laneInfo.setLane(bound, 42)
+
+        assertEquals(42, laneInfo.getLane(bound - 1))
+        assertEquals(42, laneInfo.getLane(bound))
+    }
+
+    @Test
+    fun setLane_largeNumber() {
+        laneInfo.setLane(Int.MAX_VALUE / 2, 42)
+
+        assertEquals(42, laneInfo.getLane(Int.MAX_VALUE / 2))
+    }
+
+    @Test
+    fun setLane_decreaseBound() {
+        laneInfo.setLane(Int.MAX_VALUE / 2, 42)
+        laneInfo.setLane(0, 42)
+
+        assertEquals(-1, laneInfo.getLane(Int.MAX_VALUE / 2))
+        assertEquals(42, laneInfo.getLane(0))
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun setLane_negative() {
+        laneInfo.setLane(-1, 0)
+    }
+
+    @Test
+    fun setLaneGaps() {
+        laneInfo.setLane(0, 0)
+        laneInfo.setLane(1, 1)
+        laneInfo.setGaps(0, intArrayOf(42, 24))
+        laneInfo.setGaps(1, intArrayOf(12, 21))
+
+        assertThat(laneInfo.getGaps(0)).asList().isEqualTo(listOf(42, 24))
+        assertThat(laneInfo.getGaps(1)).asList().isEqualTo(listOf(12, 21))
+    }
+
+    @Test
+    fun missingLaneGaps() {
+        laneInfo.setLane(42, 0)
+        laneInfo.setGaps(0, intArrayOf(42, 24))
+
+        assertThat(laneInfo.getGaps(42)).isNull()
+    }
+
+    @Test
+    fun clearLaneGaps() {
+        laneInfo.setLane(42, 0)
+        laneInfo.setGaps(42, intArrayOf(42, 24))
+
+        assertThat(laneInfo.getGaps(42)).isNotNull()
+
+        laneInfo.setGaps(42, null)
+        assertThat(laneInfo.getGaps(42)).isNull()
+    }
+
+    @Test
+    fun resetOnLaneInfoContentMove() {
+        laneInfo.setLane(0, 0)
+        laneInfo.setGaps(0, intArrayOf(42, 24))
+
+        laneInfo.setLane(Int.MAX_VALUE / 2, 1)
+
+        laneInfo.setGaps(0, null)
+        assertThat(laneInfo.getGaps(0)).isNull()
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridPrefetcherTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridPrefetcherTest.kt
index ffe5acc..224a1cc 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridPrefetcherTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridPrefetcherTest.kt
@@ -33,7 +33,9 @@
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsNotDisplayed
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.runBlocking
@@ -521,6 +523,133 @@
         waitForPrefetch(9)
     }
 
+    @Test
+    fun fullSpanIsPrefetchedCorrectly() {
+        val nodeConstraints = mutableMapOf<Int, Constraints>()
+        rule.setContent {
+            state = rememberLazyStaggeredGridState()
+            LazyStaggeredGrid(
+                2,
+                Modifier.mainAxisSize(itemsSizeDp * 5f).crossAxisSize(itemsSizeDp * 2f),
+                state,
+            ) {
+                items(
+                    count = 100,
+                    span = {
+                        if (it % 10 == 0)
+                            StaggeredGridItemSpan.FullLine
+                        else
+                            StaggeredGridItemSpan.SingleLane
+                    }
+                ) {
+                    DisposableEffect(it) {
+                        activeNodes.add(it)
+                        onDispose {
+                            activeNodes.remove(it)
+                            activeMeasuredNodes.remove(it)
+                        }
+                    }
+                    Spacer(
+                        Modifier
+                            .border(Dp.Hairline, Color.Black)
+                            .testTag("$it")
+                            .layout { measurable, constraints ->
+                                val placeable = measurable.measure(constraints)
+                                activeMeasuredNodes.add(it)
+                                nodeConstraints.put(it, constraints)
+                                layout(placeable.width, placeable.height) {
+                                    placeable.place(0, 0)
+                                }
+                            }
+                            .mainAxisSize(if (it == 0) itemsSizeDp else itemsSizeDp * 2)
+                    )
+                }
+            }
+        }
+
+        // ┌─┬─┐
+        // │5├─┤
+        // ├─┤6│
+        // │7├─┤
+        // ├─┤8│
+        // └─┴─┘
+
+        state.scrollBy(itemsSizeDp * 5f)
+        assertThat(activeNodes).contains(9)
+
+        waitForPrefetch(10)
+        val expectedConstraints = if (vertical) {
+            Constraints.fixedWidth(itemsSizePx * 2)
+        } else {
+            Constraints.fixedHeight(itemsSizePx * 2)
+        }
+        assertThat(nodeConstraints[10]).isEqualTo(expectedConstraints)
+    }
+
+    @Test
+    fun fullSpanIsPrefetchedCorrectly_scrollingBack() {
+        rule.setContent {
+            state = rememberLazyStaggeredGridState()
+            LazyStaggeredGrid(
+                2,
+                Modifier.mainAxisSize(itemsSizeDp * 5f),
+                state,
+            ) {
+                items(
+                    count = 100,
+                    span = {
+                        if (it % 10 == 0)
+                            StaggeredGridItemSpan.FullLine
+                        else
+                            StaggeredGridItemSpan.SingleLane
+                    }
+                ) {
+                    DisposableEffect(it) {
+                        activeNodes.add(it)
+                        onDispose {
+                            activeNodes.remove(it)
+                            activeMeasuredNodes.remove(it)
+                        }
+                    }
+                    Spacer(
+                        Modifier
+                            .mainAxisSize(if (it == 0) itemsSizeDp else itemsSizeDp * 2)
+                            .border(Dp.Hairline, Color.Black)
+                            .testTag("$it")
+                            .layout { measurable, constraints ->
+                                val placeable = measurable.measure(constraints)
+                                activeMeasuredNodes.add(it)
+                                layout(placeable.width, placeable.height) {
+                                    placeable.place(0, 0)
+                                }
+                            }
+                    )
+                }
+            }
+        }
+
+        state.scrollBy(itemsSizeDp * 13f + 2.dp)
+        rule.waitForIdle()
+
+        // second scroll to make sure subcompose marks elements as /not/ active
+        state.scrollBy(2.dp)
+        rule.waitForIdle()
+
+        // ┌──┬──┐
+        // │11│12│
+        // ├──┼──┤
+        // │13│14│
+        // ├──┼──┤
+        // └──┴──┘
+
+        assertThat(activeNodes).contains(11)
+        assertThat(activeNodes).doesNotContain(10)
+
+        state.scrollBy(-1.dp)
+
+        waitForPrefetch(10)
+    }
+
     private fun waitForPrefetch(index: Int) {
         rule.waitUntil {
             activeNodes.contains(index) && activeMeasuredNodes.contains(index)
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollTest.kt
index 49c2a4e..d52aa77 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScrollTest.kt
@@ -18,17 +18,14 @@
 
 import androidx.compose.foundation.AutoTestFrameClock
 import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.border
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
@@ -204,6 +201,16 @@
         assertThat(state.canScrollBackward).isTrue()
     }
 
+    @Test
+    fun sctollToItem_fullSpan() = runBlocking {
+        withContext(Dispatchers.Main + AutoTestFrameClock()) {
+            state.scrollToItem(49, 10)
+        }
+
+        assertThat(state.firstVisibleItemIndex).isEqualTo(49)
+        assertThat(state.firstVisibleItemScrollOffset).isEqualTo(10)
+    }
+
     @Composable
     private fun TestContent() {
         // |-|-|
@@ -220,13 +227,22 @@
             state = state,
             modifier = Modifier.axisSize(itemSizeDp * 2, itemSizeDp * 5)
         ) {
-            items(100) {
+            items(
+                count = 100,
+                span = {
+                    if (it == 50) {
+                        StaggeredGridItemSpan.FullLine
+                    } else {
+                        StaggeredGridItemSpan.SingleLane
+                    }
+                }
+            ) {
                 BasicText(
                     "$it",
                     Modifier
                         .mainAxisSize(itemSizeDp * ((it % 2) + 1))
                         .testTag("$it")
-                        .border(1.dp, Color.Black)
+                        .debugBorder()
                 )
             }
         }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSemanticTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSemanticTest.kt
index d9ba110..3ee1508 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSemanticTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSemanticTest.kt
@@ -19,6 +19,7 @@
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.text.BasicText
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsActions
@@ -97,7 +98,7 @@
                     .crossAxisSize(itemSizeDp * 2)
             ) {
                 items(items = List(ItemCount) { it }, key = { key(it) }) {
-                    Spacer(Modifier.testTag(tag(it)).mainAxisSize(itemSizeDp))
+                    BasicText("$it", Modifier.testTag(tag(it)).mainAxisSize(itemSizeDp))
                 }
             }
         }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSpansTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSpansTest.kt
deleted file mode 100644
index 63621ea..0000000
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSpansTest.kt
+++ /dev/null
@@ -1,69 +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.compose.foundation.lazy.staggeredgrid
-
-import kotlin.test.assertEquals
-import org.junit.Test
-
-class LazyStaggeredGridSpansTest {
-    private val spans = LazyStaggeredGridSpans()
-
-    @Test
-    fun emptySpan_unset() {
-        assertEquals(LazyStaggeredGridSpans.Unset, spans.getSpan(0))
-    }
-
-    @Test
-    fun setSpan() {
-        spans.setSpan(0, 42)
-        spans.setSpan(1, 0)
-
-        assertEquals(42, spans.getSpan(0))
-        assertEquals(0, spans.getSpan(1))
-    }
-
-    @Test
-    fun setSpan_beyondBound() {
-        val bound = spans.upperBound()
-        spans.setSpan(bound - 1, 42)
-        spans.setSpan(bound, 42)
-
-        assertEquals(42, spans.getSpan(bound - 1))
-        assertEquals(42, spans.getSpan(bound))
-    }
-
-    @Test
-    fun setSpan_largeNumber() {
-        spans.setSpan(Int.MAX_VALUE / 2, 42)
-
-        assertEquals(42, spans.getSpan(Int.MAX_VALUE / 2))
-    }
-
-    @Test
-    fun setSpan_decreaseBound() {
-        spans.setSpan(Int.MAX_VALUE / 2, 42)
-        spans.setSpan(0, 42)
-
-        assertEquals(-1, spans.getSpan(Int.MAX_VALUE / 2))
-        assertEquals(42, spans.getSpan(0))
-    }
-
-    @Test(expected = IllegalArgumentException::class)
-    fun setSpan_negative() {
-        spans.setSpan(-1, 0)
-    }
-}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridTest.kt
index 7eec8ab..01cadd0 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridTest.kt
@@ -35,10 +35,13 @@
 import androidx.compose.ui.test.assertCountEquals
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
 import androidx.compose.ui.test.junit4.StateRestorationTester
 import androidx.compose.ui.test.onChildren
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpOffset
+import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
@@ -851,7 +854,7 @@
     fun resizingItems_maintainsScrollingRange() {
         val state = LazyStaggeredGridState()
         var itemSizes by mutableStateOf(
-            List(20) {
+            List(10) {
                 itemSizeDp * (it % 4 + 1)
             }
         )
@@ -867,7 +870,7 @@
                     .testTag(LazyStaggeredGridTag)
                     .border(1.dp, Color.Red),
             ) {
-                items(20) {
+                items(itemSizes.size) {
                     Box(
                         Modifier
                             .axisSize(
@@ -884,24 +887,24 @@
         }
 
         rule.onNodeWithTag(LazyStaggeredGridTag)
-            .scrollMainAxisBy(itemSizeDp * 20)
+            .scrollMainAxisBy(itemSizeDp * 10)
 
-        rule.onNodeWithTag("18")
-            .assertMainAxisSizeIsEqualTo(itemSizes[18])
+        rule.onNodeWithTag("8")
+            .assertMainAxisSizeIsEqualTo(itemSizes[8])
 
-        rule.onNodeWithTag("19")
-            .assertMainAxisSizeIsEqualTo(itemSizes[19])
+        rule.onNodeWithTag("9")
+            .assertMainAxisSizeIsEqualTo(itemSizes[9])
 
         itemSizes = itemSizes.reversed()
 
-        rule.onNodeWithTag("18")
+        rule.onNodeWithTag("8")
             .assertIsDisplayed()
 
-        rule.onNodeWithTag("19")
+        rule.onNodeWithTag("9")
             .assertIsDisplayed()
 
         rule.onNodeWithTag(LazyStaggeredGridTag)
-            .scrollMainAxisBy(-itemSizeDp * 20)
+            .scrollMainAxisBy(-itemSizeDp * 10)
 
         rule.onNodeWithTag("0")
             .assertMainAxisStartPositionInRootIsEqualTo(0.dp)
@@ -996,10 +999,16 @@
 
         // check that scrolling back and forth doesn't crash
         rule.onNodeWithTag(LazyStaggeredGridTag)
-            .scrollMainAxisBy(10000.dp)
+            .scrollMainAxisBy(1000.dp)
 
         rule.onNodeWithTag(LazyStaggeredGridTag)
-            .scrollMainAxisBy(-10000.dp)
+            .scrollMainAxisBy(-1000.dp)
+
+        rule.onNodeWithTag("${Int.MAX_VALUE / 2}")
+            .assertMainAxisStartPositionInRootIsEqualTo(0.dp)
+
+        rule.onNodeWithTag("${Int.MAX_VALUE / 2 + 1}")
+            .assertMainAxisStartPositionInRootIsEqualTo(0.dp)
     }
 
     @Test
@@ -1294,4 +1303,394 @@
             }
         }
     }
+
+    @Test
+    fun fullSpan_fillsAllCrossAxisSpace() {
+        val state = LazyStaggeredGridState()
+        state.prefetchingEnabled = false
+        rule.setContentWithTestViewConfiguration {
+            LazyStaggeredGrid(
+                3,
+                Modifier
+                    .testTag(LazyStaggeredGridTag)
+                    .crossAxisSize(itemSizeDp * 3)
+                    .mainAxisSize(itemSizeDp * 10),
+                state
+            ) {
+                item(span = StaggeredGridItemSpan.FullLine) {
+                    Box(Modifier.testTag("0").mainAxisSize(itemSizeDp))
+                }
+            }
+        }
+
+        rule.onNodeWithTag("0")
+            .assertMainAxisSizeIsEqualTo(itemSizeDp)
+            .assertCrossAxisSizeIsEqualTo(itemSizeDp * 3)
+            .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+    }
+
+    @Test
+    fun fullSpan_leavesEmptyGapsWithOtherItems() {
+        val state = LazyStaggeredGridState()
+        state.prefetchingEnabled = false
+        rule.setContentWithTestViewConfiguration {
+            LazyStaggeredGrid(
+                3,
+                Modifier
+                    .testTag(LazyStaggeredGridTag)
+                    .crossAxisSize(itemSizeDp * 3)
+                    .mainAxisSize(itemSizeDp * 10),
+                state
+            ) {
+                items(2) {
+                    Box(Modifier.testTag("$it").mainAxisSize(itemSizeDp))
+                }
+
+                item(span = StaggeredGridItemSpan.FullLine) {
+                    Box(Modifier.testTag("full").mainAxisSize(itemSizeDp))
+                }
+            }
+        }
+
+        // ┌─┬─┬─┐
+        // │0│1│#│
+        // ├─┴─┴─┤
+        // │full │
+        // └─────┘
+        rule.onNodeWithTag("0")
+            .assertAxisBounds(
+                DpOffset(0.dp, 0.dp),
+                DpSize(itemSizeDp, itemSizeDp)
+            )
+
+        rule.onNodeWithTag("1")
+            .assertAxisBounds(
+                DpOffset(itemSizeDp, 0.dp),
+                DpSize(itemSizeDp, itemSizeDp)
+            )
+
+        rule.onNodeWithTag("full")
+            .assertAxisBounds(
+                DpOffset(0.dp, itemSizeDp),
+                DpSize(itemSizeDp * 3, itemSizeDp)
+            )
+    }
+
+    @Test
+    fun fullSpan_leavesGapsBetweenItems() {
+        val state = LazyStaggeredGridState()
+        state.prefetchingEnabled = false
+        rule.setContentWithTestViewConfiguration {
+            LazyStaggeredGrid(
+                3,
+                Modifier
+                    .testTag(LazyStaggeredGridTag)
+                    .crossAxisSize(itemSizeDp * 3)
+                    .mainAxisSize(itemSizeDp * 10),
+                state
+            ) {
+                items(3) {
+                    Box(Modifier.testTag("$it").mainAxisSize(itemSizeDp + itemSizeDp * it / 2))
+                }
+
+                item(span = StaggeredGridItemSpan.FullLine) {
+                    Box(Modifier.testTag("full").mainAxisSize(itemSizeDp))
+                }
+            }
+        }
+
+        // ┌───┬───┬───┐
+        // │ 0 │ 1 │ 2 │
+        // ├───┤   │   │
+        // │   └───┤   │
+        // ├───────┴───┤
+        // │   full    │
+        // └───────────┘
+        rule.onNodeWithTag("0")
+            .assertAxisBounds(
+                DpOffset(0.dp, 0.dp),
+                DpSize(itemSizeDp, itemSizeDp)
+            )
+
+        rule.onNodeWithTag("1")
+            .assertAxisBounds(
+                DpOffset(itemSizeDp, 0.dp),
+                DpSize(itemSizeDp, itemSizeDp * 1.5f)
+            )
+
+        rule.onNodeWithTag("2")
+            .assertAxisBounds(
+                DpOffset(itemSizeDp * 2, 0.dp),
+                DpSize(itemSizeDp, itemSizeDp * 2f)
+            )
+
+        rule.onNodeWithTag("full")
+            .assertAxisBounds(
+                DpOffset(0.dp, itemSizeDp * 2f),
+                DpSize(itemSizeDp * 3, itemSizeDp)
+            )
+    }
+
+    @Test
+    fun fullSpan_scrollsCorrectly() {
+        val state = LazyStaggeredGridState()
+        state.prefetchingEnabled = false
+        rule.setContentWithTestViewConfiguration {
+            LazyStaggeredGrid(
+                3,
+                Modifier
+                    .testTag(LazyStaggeredGridTag)
+                    .crossAxisSize(itemSizeDp * 3)
+                    .mainAxisSize(itemSizeDp * 2),
+                state
+            ) {
+                items(3) {
+                    Box(
+                        Modifier
+                            .testTag("$it")
+                            .mainAxisSize(itemSizeDp + itemSizeDp * it / 2)
+                    )
+                }
+
+                item(span = StaggeredGridItemSpan.FullLine) {
+                    Box(Modifier.testTag("full").mainAxisSize(itemSizeDp))
+                }
+
+                items(3) {
+                    Box(
+                        Modifier
+                            .testTag("${it + 3}")
+                            .mainAxisSize(itemSizeDp + itemSizeDp * it / 2)
+                    )
+                }
+
+                item(span = StaggeredGridItemSpan.FullLine) {
+                    Box(Modifier.testTag("full-2").mainAxisSize(itemSizeDp))
+                }
+            }
+        }
+
+        // ┌───┬───┬───┐
+        // │ 0 │ 1 │ 2 │
+        // ├───┤   │   │
+        // │   └───┤   │
+        // ├───────┴───┤ <-- scroll offset
+        // │   full    │
+        // ├───┬───┬───┤
+        // │ 3 │ 4 │ 5 │
+        // ├───┤   │   │ <-- end of screen
+        // │   └───┤   │
+        // ├───────┴───┤
+        // │   full-2  │
+        // └───────────┘
+        rule.onNodeWithTag(LazyStaggeredGridTag)
+            .scrollMainAxisBy(itemSizeDp * 2f)
+
+        rule.onNodeWithTag("full")
+            .assertAxisBounds(
+                DpOffset(0.dp, 0.dp),
+                DpSize(itemSizeDp * 3, itemSizeDp)
+            )
+
+        assertThat(state.firstVisibleItemIndex).isEqualTo(3)
+        assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
+    }
+
+    @Test
+    fun fullSpan_scrollsCorrectly_pastFullSpan() {
+        val state = LazyStaggeredGridState()
+        state.prefetchingEnabled = false
+        rule.setContentWithTestViewConfiguration {
+            LazyStaggeredGrid(
+                3,
+                Modifier
+                    .testTag(LazyStaggeredGridTag)
+                    .crossAxisSize(itemSizeDp * 3)
+                    .mainAxisSize(itemSizeDp * 2),
+                state
+            ) {
+                repeat(10) { repeatIndex ->
+                    items(3) {
+                        Box(
+                            Modifier
+                                .testTag("${repeatIndex * 3 + it}")
+                                .mainAxisSize(itemSizeDp + itemSizeDp * it / 2)
+                        )
+                    }
+
+                    item(span = StaggeredGridItemSpan.FullLine) {
+                        Box(Modifier.testTag("full-$repeatIndex").mainAxisSize(itemSizeDp))
+                    }
+                }
+            }
+        }
+
+        // ┌───┬───┬───┐
+        // │ 0 │ 1 │ 2 │
+        // ├───┤   │   │
+        // │   └───┤   │
+        // ├───────┴───┤
+        // │   full-0  │
+        // ├───┬───┬───┤  <-- scroll offset
+        // │ 3 │ 4 │ 5 │
+        // ├───┤   │   │
+        // │   └───┤   │
+        // ├───────┴───┤  <-- end of screen
+        // │   full-1  │
+        // └───────────┘
+        rule.onNodeWithTag(LazyStaggeredGridTag)
+            .scrollMainAxisBy(itemSizeDp * 3f)
+
+        rule.onNodeWithTag("3")
+            .assertAxisBounds(
+                DpOffset(0.dp, 0.dp),
+                DpSize(itemSizeDp, itemSizeDp)
+            )
+
+        rule.onNodeWithTag("4")
+            .assertAxisBounds(
+                DpOffset(itemSizeDp, 0.dp),
+                DpSize(itemSizeDp, itemSizeDp * 1.5f)
+            )
+
+        rule.onNodeWithTag("5")
+            .assertAxisBounds(
+                DpOffset(itemSizeDp * 2, 0.dp),
+                DpSize(itemSizeDp, itemSizeDp * 2)
+            )
+
+        assertThat(state.firstVisibleItemIndex).isEqualTo(4)
+        assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
+    }
+
+    @Test
+    fun fullSpan_scrollsCorrectly_pastFullSpan_andBack() {
+        val state = LazyStaggeredGridState()
+        state.prefetchingEnabled = false
+        rule.setContentWithTestViewConfiguration {
+            LazyStaggeredGrid(
+                3,
+                Modifier
+                    .testTag(LazyStaggeredGridTag)
+                    .crossAxisSize(itemSizeDp * 3)
+                    .mainAxisSize(itemSizeDp * 2),
+                state
+            ) {
+                repeat(10) { repeatIndex ->
+                    items(3) {
+                        Box(
+                            Modifier
+                                .testTag("${repeatIndex * 3 + it}")
+                                .mainAxisSize(itemSizeDp + itemSizeDp * it / 2)
+                        )
+                    }
+
+                    item(span = StaggeredGridItemSpan.FullLine) {
+                        Box(Modifier.testTag("full-$repeatIndex").mainAxisSize(itemSizeDp))
+                    }
+                }
+            }
+        }
+
+        rule.onNodeWithTag(LazyStaggeredGridTag)
+            .scrollMainAxisBy(itemSizeDp * 3f)
+
+        rule.onNodeWithTag(LazyStaggeredGridTag)
+            .scrollMainAxisBy(-itemSizeDp * 3f)
+
+        // ┌───┬───┬───┐  <-- scroll offset
+        // │ 0 │ 1 │ 2 │
+        // ├───┤   │   │
+        // │   └───┤   │
+        // ├───────┴───┤  <-- end of screen
+        // │   full-0  │
+        // ├───┬───┬───┤
+        // │ 3 │ 4 │ 5 │
+        // ├───┤   │   │
+        // │   └───┤   │
+        // ├───────┴───┤
+        // │   full-1  │
+        // └───────────┘
+
+        rule.onNodeWithTag("0")
+            .assertAxisBounds(
+                DpOffset(0.dp, 0.dp),
+                DpSize(itemSizeDp, itemSizeDp)
+            )
+
+        rule.onNodeWithTag("1")
+            .assertAxisBounds(
+                DpOffset(itemSizeDp, 0.dp),
+                DpSize(itemSizeDp, itemSizeDp * 1.5f)
+            )
+
+        rule.onNodeWithTag("2")
+            .assertAxisBounds(
+                DpOffset(itemSizeDp * 2, 0.dp),
+                DpSize(itemSizeDp, itemSizeDp * 2)
+            )
+
+        assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+        assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
+    }
+
+    @Test
+    fun fullSpan_scrollsCorrectly_multipleFullSpans() {
+        val state = LazyStaggeredGridState()
+        state.prefetchingEnabled = false
+        rule.setContentWithTestViewConfiguration {
+            LazyStaggeredGrid(
+                3,
+                Modifier
+                    .testTag(LazyStaggeredGridTag)
+                    .crossAxisSize(itemSizeDp * 3)
+                    .mainAxisSize(itemSizeDp * 2),
+                state
+            ) {
+                items(10, span = { StaggeredGridItemSpan.FullLine }) {
+                    Box(
+                        Modifier
+                            .testTag("$it")
+                            .mainAxisSize(itemSizeDp)
+                    )
+                }
+            }
+        }
+
+        rule.onNodeWithTag(LazyStaggeredGridTag)
+            .scrollMainAxisBy(itemSizeDp * 3f)
+
+        rule.onNodeWithTag("3")
+            .assertAxisBounds(
+                DpOffset(0.dp, 0.dp),
+                DpSize(itemSizeDp * 3, itemSizeDp)
+            )
+
+        rule.onNodeWithTag("4")
+            .assertAxisBounds(
+                DpOffset(0.dp, itemSizeDp),
+                DpSize(itemSizeDp * 3, itemSizeDp)
+            )
+
+        assertThat(state.firstVisibleItemIndex).isEqualTo(3)
+        assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
+
+        rule.onNodeWithTag(LazyStaggeredGridTag)
+            .scrollMainAxisBy(itemSizeDp * 10f)
+
+        rule.onNodeWithTag("8")
+            .assertAxisBounds(
+                DpOffset(0.dp, 0.dp),
+                DpSize(itemSizeDp * 3, itemSizeDp)
+            )
+
+        rule.onNodeWithTag("9")
+            .assertAxisBounds(
+                DpOffset(0.dp, itemSizeDp),
+                DpSize(itemSizeDp * 3, itemSizeDp)
+            )
+
+        assertThat(state.firstVisibleItemIndex).isEqualTo(8)
+        assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
+    }
 }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/ClickableTextTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/ClickableTextTest.kt
index 1ffef6c..8bba29a2 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/ClickableTextTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/ClickableTextTest.kt
@@ -16,19 +16,26 @@
 
 package androidx.compose.foundation.text
 
+import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performMouseInput
 import androidx.compose.ui.text.AnnotatedString
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.argWhere
+import com.nhaarman.mockitokotlin2.inOrder
 import com.nhaarman.mockitokotlin2.mock
 import com.nhaarman.mockitokotlin2.times
 import com.nhaarman.mockitokotlin2.verify
+import com.nhaarman.mockitokotlin2.verifyZeroInteractions
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -79,4 +86,40 @@
             verify(onClick2, times(1)).invoke(any())
         }
     }
+
+    @OptIn(ExperimentalFoundationApi::class, ExperimentalTestApi::class)
+    @Test
+    fun onhover_callback() {
+        val onHover: (Int?) -> Unit = mock()
+        val onClick: (Int) -> Unit = mock()
+        rule.setContent {
+            ClickableText(
+                modifier = Modifier.testTag("clickableText"),
+                text = AnnotatedString("android"),
+                onHover = onHover,
+                onClick = onClick,
+            )
+        }
+
+        rule.onNodeWithTag("clickableText")
+            .performMouseInput {
+                moveTo(Offset(-1f, -1f), 0) // outside bounds
+                moveTo(Offset(1f, 1f), 0) // inside bounds
+                moveTo(Offset(-1f, -1f), 0) // outside bounds again
+                moveTo(Offset(1f, 1f), 0) // inside bounds again
+                moveTo(Offset(1f, 2f), 0) // move but stay on the same character
+                moveTo(Offset(50f, 1f), 0) // move to different character
+            }
+
+        rule.runOnIdle {
+            onHover.inOrder {
+                verify().invoke(0) // first enter
+                verify().invoke(null) // first exit
+                verify().invoke(0) // second enter
+                verify().invoke(argWhere { it > 0 }) // move to different character
+                verifyNoMoreInteractions()
+            }
+            verifyZeroInteractions(onClick)
+        }
+    }
 }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldCursorTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldCursorTest.kt
index 319f135..3e2c1f1 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldCursorTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldCursorTest.kt
@@ -58,6 +58,7 @@
 import com.google.common.truth.Truth.assertThat
 import kotlin.math.ceil
 import kotlin.math.floor
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 
@@ -205,6 +206,50 @@
             )
     }
 
+    // TODO(b/265177763) Update this test to set MotionDurationScale to 0 when that's supported.
+    @Ignore("b/265177763")
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun cursorBlinkingAnimation_whenSystemDisablesAnimations() = with(rule.density) {
+        rule.setContent {
+            // The padding helps if the test is run accidentally in landscape. Landscape makes
+            // the cursor to be next to the navigation bar which affects the red color to be a bit
+            // different - possibly anti-aliasing.
+            Box(Modifier.padding(boxPadding)) {
+                BasicTextField(
+                    value = "",
+                    onValueChange = {},
+                    textStyle = textStyle,
+                    modifier = textFieldModifier,
+                    cursorBrush = SolidColor(cursorColor),
+                    onTextLayout = onTextLayout
+                )
+            }
+        }
+
+        focusAndWait()
+
+        // cursor visible first 500 ms
+        rule.mainClock.advanceTimeBy(100)
+        with(rule.density) {
+            rule.onNode(hasSetTextAction())
+                .captureToImage()
+                .assertCursor(2.dp, this, cursorRect)
+        }
+
+        // cursor invisible during next 500 ms
+        rule.mainClock.advanceTimeBy(700)
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = RectangleShape,
+                shapeColor = Color.White,
+                backgroundColor = Color.White,
+                shapeOverlapPixelCount = 0.0f
+            )
+    }
+
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     fun cursorUnsetColor_noCursor() = with(rule.density) {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldFocusTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldFocusTest.kt
index 2ad596e..02827ab 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldFocusTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldFocusTest.kt
@@ -68,6 +68,7 @@
 import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
 import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -380,6 +381,7 @@
         rule.onNodeWithTag("test-button-bottom").assertIsFocused()
     }
 
+    @Ignore // b/264919150
     @Test
     fun basicTextField_checkKeyboardShown_onDPadCenter() {
         setupAndEnableBasicTextField()
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldSelectionTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldSelectionTest.kt
index 8656483..4130a1c 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldSelectionTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldSelectionTest.kt
@@ -43,6 +43,7 @@
 import androidx.compose.ui.text.input.VisualTransformation
 import com.google.common.truth.Truth.assertThat
 import kotlin.math.roundToInt
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 
@@ -215,6 +216,7 @@
         )
     }
 
+    @Ignore // b/265023621
     @Test
     fun textField_extendsSelection_withPasswordVisualTransformation_toRight() {
         textField_extendsSelection(
@@ -225,6 +227,7 @@
         )
     }
 
+    @Ignore // b/265023621
     @Test
     fun textField_extendsSelection_withReducedVisualTransformation_toRight() {
         textField_extendsSelection(
@@ -238,6 +241,7 @@
         )
     }
 
+    @Ignore // b/265023420
     @Test
     fun textField_extendsSelection_toLeft() {
         textField_extendsSelection(
@@ -248,6 +252,7 @@
         )
     }
 
+    @Ignore // b/265023621
     @Test
     fun textField_extendsSelection_withPasswordVisualTransformation_toLeft() {
         textField_extendsSelection(
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldVisualTransformationSelectionBoundsTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldVisualTransformationSelectionBoundsTest.kt
index bc1c269..c582ec6 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldVisualTransformationSelectionBoundsTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldVisualTransformationSelectionBoundsTest.kt
@@ -44,6 +44,7 @@
 import kotlin.math.roundToInt
 import kotlin.test.assertFailsWith
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -242,6 +243,7 @@
         assertValidMessage(error, sourceIndex = 0, toTransformed = false)
     }
 
+    @Ignore // b/265019668
     @Test
     fun selectionEnd_throws_onDrag_whenInvalidOriginalToTransformed() {
         rule.onNodeWithTag(testTag).performTouchInput { longClick() }
@@ -257,6 +259,7 @@
         assertValidMessage(error, sourceIndex = text.length, toTransformed = true)
     }
 
+    @Ignore // b/265019668
     @Test
     fun selectionEnd_throws_onDrag_whenInvalidTransformedToOriginal() {
         rule.onNodeWithTag(testTag).performTouchInput { longClick() }
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Clickable.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Clickable.android.kt
index 79247de..6b5cef6 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Clickable.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/Clickable.android.kt
@@ -19,13 +19,37 @@
 import android.view.KeyEvent.KEYCODE_DPAD_CENTER
 import android.view.KeyEvent.KEYCODE_ENTER
 import android.view.KeyEvent.KEYCODE_NUMPAD_ENTER
+import android.view.View
 import android.view.ViewConfiguration
+import android.view.ViewGroup
+import androidx.compose.runtime.Composable
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
 import androidx.compose.ui.input.key.KeyEventType.Companion.KeyUp
 import androidx.compose.ui.input.key.key
 import androidx.compose.ui.input.key.nativeKeyCode
 import androidx.compose.ui.input.key.type
+import androidx.compose.ui.platform.LocalView
+
+@Composable
+internal actual fun isComposeRootInScrollableContainer(): () -> Boolean {
+    val view = LocalView.current
+    return {
+        view.isInScrollableViewGroup()
+    }
+}
+
+// Copied from View#isInScrollingContainer() which is @hide
+private fun View.isInScrollableViewGroup(): Boolean {
+    var p = parent
+    while (p != null && p is ViewGroup) {
+        if (p.shouldDelayChildPressedState()) {
+            return true
+        }
+        p = p.parent
+    }
+    return false
+}
 
 internal actual val TapIndicationDelay: Long = ViewConfiguration.getTapTimeout().toLong()
 
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher.android.kt
index be9c1fa..48c54d1 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPrefetcher.android.kt
@@ -268,7 +268,8 @@
     private class PrefetchRequest(
         val index: Int,
         val constraints: Constraints
-    ) : LazyLayoutPrefetchState.PrefetchHandle {
+    ) : @Suppress("SEALED_INHERITOR_IN_DIFFERENT_MODULE")
+    LazyLayoutPrefetchState.PrefetchHandle {
 
         var precomposeHandle: PrecomposedSlotHandle? = null
         var canceled = false
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicMarquee.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicMarquee.kt
index d1b621f..048508f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicMarquee.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/BasicMarquee.kt
@@ -26,6 +26,7 @@
 import androidx.compose.animation.core.infiniteRepeatable
 import androidx.compose.animation.core.repeatable
 import androidx.compose.animation.core.tween
+import androidx.compose.foundation.FixedMotionDurationScale.scaleFactor
 import androidx.compose.foundation.MarqueeAnimationMode.Companion.Immediately
 import androidx.compose.foundation.MarqueeAnimationMode.Companion.WhileFocused
 import androidx.compose.runtime.LaunchedEffect
@@ -37,6 +38,7 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.MotionDurationScale
 import androidx.compose.ui.composed
 import androidx.compose.ui.draw.DrawModifier
 import androidx.compose.ui.focus.FocusState
@@ -62,6 +64,7 @@
 import kotlin.math.roundToInt
 import kotlin.math.sign
 import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.withContext
 
 // From https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/widget/TextView.java;l=736;drc=6d97d6d7215fef247d1a90e05545cac3676f9212
 @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
@@ -256,29 +259,35 @@
             return
         }
 
-        snapshotFlow {
-            // Don't animate if content fits. (Because coroutines, the int will get boxed anyway.)
-            if (contentWidth <= containerWidth) return@snapshotFlow null
-            if (animationMode == WhileFocused && !hasFocus) return@snapshotFlow null
-            (contentWidth + spacingPx).toFloat()
-        }.collectLatest { contentWithSpacingWidth ->
-            // Don't animate when the content fits.
-            if (contentWithSpacingWidth == null) return@collectLatest
+        // Marquee animations should not be affected by motion accessibility settings.
+        // Wrap the entire flow instead of just the animation calls so kotlin doesn't have to create
+        // an extra CoroutineContext every time the flow emits.
+        withContext(FixedMotionDurationScale) {
+            snapshotFlow {
+                // Don't animate if content fits. (Because coroutines, the int will get boxed
+                // anyway.)
+                if (contentWidth <= containerWidth) return@snapshotFlow null
+                if (animationMode == WhileFocused && !hasFocus) return@snapshotFlow null
+                (contentWidth + spacingPx).toFloat()
+            }.collectLatest { contentWithSpacingWidth ->
+                // Don't animate when the content fits.
+                if (contentWithSpacingWidth == null) return@collectLatest
 
-            val spec = createMarqueeAnimationSpec(
-                iterations,
-                contentWithSpacingWidth,
-                initialDelayMillis,
-                delayMillis,
-                velocity,
-                density
-            )
+                val spec = createMarqueeAnimationSpec(
+                    iterations,
+                    contentWithSpacingWidth,
+                    initialDelayMillis,
+                    delayMillis,
+                    velocity,
+                    density
+                )
 
-            offset.snapTo(0f)
-            try {
-                offset.animateTo(contentWithSpacingWidth, spec)
-            } finally {
                 offset.snapTo(0f)
+                try {
+                    offset.animateTo(contentWithSpacingWidth, spec)
+                } finally {
+                    offset.snapTo(0f)
+                }
             }
         }
     }
@@ -399,4 +408,10 @@
             (fraction * width).roundToInt()
         }
     }
+}
+
+/** A [MotionDurationScale] that always reports a [scaleFactor] of 1. */
+private object FixedMotionDurationScale : MotionDurationScale {
+    override val scaleFactor: Float
+        get() = 1f
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
index 64643f1..b249514 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation
 
+import androidx.compose.foundation.gestures.ModifierLocalScrollableContainer
 import androidx.compose.foundation.gestures.PressGestureScope
 import androidx.compose.foundation.gestures.detectTapAndPress
 import androidx.compose.foundation.gestures.detectTapGestures
@@ -37,8 +38,8 @@
 import androidx.compose.ui.input.key.key
 import androidx.compose.ui.input.key.onKeyEvent
 import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.input.canScroll
-import androidx.compose.ui.input.consumeScrollContainerInfo
+import androidx.compose.ui.modifier.ModifierLocalConsumer
+import androidx.compose.ui.modifier.ModifierLocalReadScope
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.disabled
@@ -143,8 +144,11 @@
                 currentKeyPressInteractions
             )
         }
-
-        val delayPressInteraction = remember { mutableStateOf({ true }) }
+        val isRootInScrollableContainer = isComposeRootInScrollableContainer()
+        val isClickableInScrollableContainer = remember { mutableStateOf(true) }
+        val delayPressInteraction = rememberUpdatedState {
+            isClickableInScrollableContainer.value || isRootInScrollableContainer()
+        }
         val centreOffset = remember { mutableStateOf(Offset.Zero) }
 
         val gesture = Modifier.pointerInput(interactionSource, enabled) {
@@ -164,9 +168,18 @@
             )
         }
         Modifier
-            .consumeScrollContainerInfo { scrollContainerInfo ->
-                delayPressInteraction.value = { scrollContainerInfo?.canScroll() == true }
-            }
+            .then(
+                remember {
+                    object : ModifierLocalConsumer {
+                        override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) {
+                            with(scope) {
+                                isClickableInScrollableContainer.value =
+                                    ModifierLocalScrollableContainer.current
+                            }
+                        }
+                    }
+                }
+            )
             .genericClickableWithoutGesture(
                 gestureModifiers = gesture,
                 interactionSource = interactionSource,
@@ -317,9 +330,13 @@
                 currentKeyPressInteractions
             )
         }
-
-        val delayPressInteraction = remember { mutableStateOf({ true }) }
+        val isRootInScrollableContainer = isComposeRootInScrollableContainer()
+        val isClickableInScrollableContainer = remember { mutableStateOf(true) }
+        val delayPressInteraction = rememberUpdatedState {
+            isClickableInScrollableContainer.value || isRootInScrollableContainer()
+        }
         val centreOffset = remember { mutableStateOf(Offset.Zero) }
+
         val gesture =
             Modifier.pointerInput(interactionSource, hasLongClick, hasDoubleClick, enabled) {
                 centreOffset.value = size.center.toOffset()
@@ -348,6 +365,18 @@
                 )
             }
         Modifier
+            .then(
+                remember {
+                    object : ModifierLocalConsumer {
+                        override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) {
+                            with(scope) {
+                                isClickableInScrollableContainer.value =
+                                    ModifierLocalScrollableContainer.current
+                            }
+                        }
+                    }
+                }
+            )
             .genericClickableWithoutGesture(
                 gestureModifiers = gesture,
                 interactionSource = interactionSource,
@@ -362,9 +391,6 @@
                 onLongClick = onLongClick,
                 onClick = onClick
             )
-            .consumeScrollContainerInfo { scrollContainerInfo ->
-                delayPressInteraction.value = { scrollContainerInfo?.canScroll() == true }
-            }
     },
     inspectorInfo = debugInspectorInfo {
         name = "combinedClickable"
@@ -449,6 +475,20 @@
 internal expect val TapIndicationDelay: Long
 
 /**
+ * Returns a lambda that calculates whether the root Compose layout node is hosted in a scrollable
+ * container outside of Compose. On Android this will be whether the root View is in a scrollable
+ * ViewGroup, as even if nothing in the Compose part of the hierarchy is scrollable, if the View
+ * itself is in a scrollable container, we still want to delay presses in case presses in Compose
+ * convert to a scroll outside of Compose.
+ *
+ * Combine this with [ModifierLocalScrollableContainer], which returns whether a [Modifier] is
+ * within a scrollable Compose layout, to calculate whether this modifier is within some form of
+ * scrollable container, and hence should delay presses.
+ */
+@Composable
+internal expect fun isComposeRootInScrollableContainer(): () -> Boolean
+
+/**
  * Whether the specified [KeyEvent] should trigger a press for a clickable component.
  */
 internal expect val KeyEvent.isPress: Boolean
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
index 0be2c7b..020772e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Draggable.kt
@@ -24,7 +24,6 @@
 import androidx.compose.foundation.gestures.DragEvent.DragStopped
 import androidx.compose.foundation.interaction.DragInteraction
 import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.internal.JvmDefaultWithCompatibility
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
@@ -56,6 +55,7 @@
 import kotlinx.coroutines.channels.SendChannel
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.isActive
+import androidx.compose.foundation.internal.JvmDefaultWithCompatibility
 
 /**
  * State of [draggable]. Allows for a granular control of how deltas are consumed by the user as
@@ -258,7 +258,6 @@
             }
         }
     }
-
     Modifier.pointerInput(orientation, enabled, reverseDirection) {
         if (!enabled) return@pointerInput
         coroutineScope {
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 3876703..4057fb4 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
@@ -37,7 +37,6 @@
 import androidx.compose.ui.MotionDurationScale
 import androidx.compose.ui.composed
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.ScrollContainerInfo
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
@@ -49,7 +48,8 @@
 import androidx.compose.ui.input.pointer.PointerEventType
 import androidx.compose.ui.input.pointer.PointerType
 import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.input.provideScrollContainerInfo
+import androidx.compose.ui.modifier.ModifierLocalProvider
+import androidx.compose.ui.modifier.modifierLocalOf
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.unit.Density
@@ -172,6 +172,7 @@
                 overscrollEffect,
                 enabled
             )
+            .then(if (enabled) ModifierLocalScrollableContainerProvider else Modifier)
     }
 )
 
@@ -265,14 +266,6 @@
     val draggableState = remember { ScrollDraggableState(scrollLogic) }
     val scrollConfig = platformScrollConfig()
 
-    val scrollContainerInfo = remember(orientation, enabled) {
-        object : ScrollContainerInfo {
-            override fun canScrollHorizontally() = enabled && orientation == Horizontal
-
-            override fun canScrollVertically() = enabled && orientation == Orientation.Vertical
-        }
-    }
-
     return draggable(
         draggableState,
         orientation = orientation,
@@ -289,7 +282,6 @@
     )
         .mouseWheelScroll(scrollLogic, scrollConfig)
         .nestedScroll(nestedScrollConnection, nestedScrollDispatcher.value)
-        .provideScrollContainerInfo(scrollContainerInfo)
 }
 
 private fun Modifier.mouseWheelScroll(
@@ -591,9 +583,21 @@
     }
 }
 
+// TODO: b/203141462 - make this public and move it to ui
+/**
+ * Whether this modifier is inside a scrollable container, provided by [Modifier.scrollable].
+ * Defaults to false.
+ */
+internal val ModifierLocalScrollableContainer = modifierLocalOf { false }
+
+private object ModifierLocalScrollableContainerProvider : ModifierLocalProvider<Boolean> {
+    override val key = ModifierLocalScrollableContainer
+    override val value = true
+}
+
 private const val DefaultScrollMotionDurationScaleFactor = 1f
 
 internal val DefaultScrollMotionDurationScale = object : MotionDurationScale {
     override val scaleFactor: Float
         get() = DefaultScrollMotionDurationScaleFactor
-}
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Transformable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Transformable.kt
index 397ee29..e34a548 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Transformable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Transformable.kt
@@ -17,6 +17,10 @@
 package androidx.compose.foundation.gestures
 
 import androidx.compose.foundation.MutatePriority
+import androidx.compose.foundation.gestures.TransformEvent.TransformDelta
+import androidx.compose.foundation.gestures.TransformEvent.TransformStarted
+import androidx.compose.foundation.gestures.TransformEvent.TransformStopped
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.State
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberUpdatedState
@@ -24,22 +28,18 @@
 import androidx.compose.ui.composed
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.pointer.AwaitPointerEventScope
-import androidx.compose.ui.input.pointer.PointerEvent
-import androidx.compose.ui.input.pointer.PointerEventPass
-import androidx.compose.ui.input.pointer.PointerId
 import androidx.compose.ui.input.pointer.PointerInputScope
-import androidx.compose.ui.input.pointer.changedToDown
-import androidx.compose.ui.input.pointer.changedToDownIgnoreConsumed
-import androidx.compose.ui.input.pointer.changedToUp
-import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.input.pointer.positionChanged
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.util.fastAny
 import androidx.compose.ui.util.fastForEach
-import kotlinx.coroutines.CancellationException
 import kotlin.math.PI
 import kotlin.math.abs
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.isActive
 
 /**
  * Enable transformation gestures of the modified UI element.
@@ -64,20 +64,40 @@
     enabled: Boolean = true
 ) = composed(
     factory = {
-        val updatedState = rememberUpdatedState(state)
         val updatePanZoomLock = rememberUpdatedState(lockRotationOnZoomPan)
+        val channel = remember { Channel<TransformEvent>(capacity = Channel.UNLIMITED) }
+        if (enabled) {
+            LaunchedEffect(state) {
+                while (isActive) {
+                    var event = channel.receive()
+                    if (event !is TransformStarted) continue
+                    try {
+                        state.transform(MutatePriority.UserInput) {
+                            while (event !is TransformStopped) {
+                                (event as? TransformDelta)?.let {
+                                    transformBy(it.zoomChange, it.panChange, it.rotationChange)
+                                }
+                                event = channel.receive()
+                            }
+                        }
+                    } catch (_: CancellationException) {
+                        // ignore the cancellation and start over again.
+                    }
+                }
+            }
+        }
         val block: suspend PointerInputScope.() -> Unit = remember {
             {
-                /**
-                 * This cannot be converted to awaitEachGesture() because
-                 * [TransformableState.transform] is a suspend function. Unfortunately, this means
-                 * that events can be lost in the middle of a gesture.
-                 *
-                 * TODO(b/251826790) Convert to awaitEachGesture()
-                 */
-                @Suppress("DEPRECATION")
-                forEachGesture {
-                    detectZoom(updatePanZoomLock, updatedState)
+                coroutineScope {
+                    awaitEachGesture {
+                        try {
+                            detectZoom(updatePanZoomLock, channel)
+                        } catch (exception: CancellationException) {
+                            if (!isActive) throw exception
+                        } finally {
+                            channel.trySend(TransformStopped)
+                        }
+                    }
                 }
             }
         }
@@ -91,11 +111,19 @@
     }
 )
 
-// b/242023503 for a planned fix for the MultipleAwaitPointerEventScopes lint violation.
-@Suppress("MultipleAwaitPointerEventScopes")
-private suspend fun PointerInputScope.detectZoom(
+private sealed class TransformEvent {
+    object TransformStarted : TransformEvent()
+    object TransformStopped : TransformEvent()
+    class TransformDelta(
+        val zoomChange: Float,
+        val panChange: Offset,
+        val rotationChange: Float
+    ) : TransformEvent()
+}
+
+private suspend fun AwaitPointerEventScope.detectZoom(
     panZoomLock: State<Boolean>,
-    state: State<TransformableState>
+    channel: Channel<TransformEvent>
 ) {
     var rotation = 0f
     var zoom = 1f
@@ -103,86 +131,49 @@
     var pastTouchSlop = false
     val touchSlop = viewConfiguration.touchSlop
     var lockedToPanZoom = false
-    awaitPointerEventScope {
-        awaitTwoDowns(requireUnconsumed = false)
-    }
-    try {
-        state.value.transform(MutatePriority.UserInput) {
-            awaitPointerEventScope {
-                do {
-                    val event = awaitPointerEvent()
-                    val canceled = event.changes.fastAny { it.isConsumed }
-                    if (!canceled) {
-                        val zoomChange = event.calculateZoom()
-                        val rotationChange = event.calculateRotation()
-                        val panChange = event.calculatePan()
-
-                        if (!pastTouchSlop) {
-                            zoom *= zoomChange
-                            rotation += rotationChange
-                            pan += panChange
-
-                            val centroidSize = event.calculateCentroidSize(useCurrent = false)
-                            val zoomMotion = abs(1 - zoom) * centroidSize
-                            val rotationMotion = abs(rotation * PI.toFloat() * centroidSize / 180f)
-                            val panMotion = pan.getDistance()
-
-                            if (zoomMotion > touchSlop ||
-                                rotationMotion > touchSlop ||
-                                panMotion > touchSlop
-                            ) {
-                                pastTouchSlop = true
-                                lockedToPanZoom = panZoomLock.value && rotationMotion < touchSlop
-                            }
-                        }
-
-                        if (pastTouchSlop) {
-                            val effectiveRotation = if (lockedToPanZoom) 0f else rotationChange
-                            if (effectiveRotation != 0f ||
-                                zoomChange != 1f ||
-                                panChange != Offset.Zero
-                            ) {
-                                transformBy(zoomChange, panChange, effectiveRotation)
-                            }
-                            event.changes.fastForEach {
-                                if (it.positionChanged()) {
-                                    it.consume()
-                                }
-                            }
-                        }
-                    }
-                } while (!canceled && event.changes.fastAny { it.pressed })
-            }
-        }
-    } catch (c: CancellationException) {
-        // cancelled by higher priority, start listening over
-    }
-}
-
-/**
- * Reads events until the first down is received. If [requireUnconsumed] is `true` and the first
- * down is consumed in the [PointerEventPass.Main] pass, that gesture is ignored.
- */
-private suspend fun AwaitPointerEventScope.awaitTwoDowns(requireUnconsumed: Boolean = true) {
-    var event: PointerEvent
-    var firstDown: PointerId? = null
+    awaitFirstDown(requireUnconsumed = false)
     do {
-        event = awaitPointerEvent()
-        var downPointers = if (firstDown != null) 1 else 0
-        event.changes.fastForEach {
-            val isDown =
-                if (requireUnconsumed) it.changedToDown() else it.changedToDownIgnoreConsumed()
-            val isUp =
-                if (requireUnconsumed) it.changedToUp() else it.changedToUpIgnoreConsumed()
-            if (isUp && firstDown == it.id) {
-                firstDown = null
-                downPointers -= 1
+        val event = awaitPointerEvent()
+        val canceled = event.changes.fastAny { it.isConsumed }
+        if (!canceled) {
+            val zoomChange = event.calculateZoom()
+            val rotationChange = event.calculateRotation()
+            val panChange = event.calculatePan()
+
+            if (!pastTouchSlop) {
+                zoom *= zoomChange
+                rotation += rotationChange
+                pan += panChange
+
+                val centroidSize = event.calculateCentroidSize(useCurrent = false)
+                val zoomMotion = abs(1 - zoom) * centroidSize
+                val rotationMotion = abs(rotation * PI.toFloat() * centroidSize / 180f)
+                val panMotion = pan.getDistance()
+
+                if (zoomMotion > touchSlop ||
+                    rotationMotion > touchSlop ||
+                    panMotion > touchSlop
+                ) {
+                    pastTouchSlop = true
+                    lockedToPanZoom = panZoomLock.value && rotationMotion < touchSlop
+                    channel.trySend(TransformStarted)
+                }
             }
-            if (isDown) {
-                firstDown = it.id
-                downPointers += 1
+
+            if (pastTouchSlop) {
+                val effectiveRotation = if (lockedToPanZoom) 0f else rotationChange
+                if (effectiveRotation != 0f ||
+                    zoomChange != 1f ||
+                    panChange != Offset.Zero
+                ) {
+                    channel.trySend(TransformDelta(zoomChange, panChange, effectiveRotation))
+                }
+                event.changes.fastForEach {
+                    if (it.positionChanged()) {
+                        it.consume()
+                    }
+                }
             }
         }
-        val satisfied = downPointers > 1
-    } while (!satisfied)
-}
\ No newline at end of file
+    } while (!canceled && event.changes.fastAny { it.pressed })
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt
index 28f690f..1a506df 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt
@@ -214,7 +214,7 @@
     /**
      * Add a single item to the staggered grid.
      *
-     * @param key a factory of stable and unique keys representing the item. The key
+     * @param key a stable and unique key representing the item. The key
      *  MUST be saveable via Bundle on Android. If set to null (by default), the position of the
      *  item will be used as a key instead.
      *  Using the same key for multiple items in the staggered grid is not allowed.
@@ -222,15 +222,18 @@
      *  When you specify the key the scroll position will be maintained based on the key, which
      *  means if you add/remove items before the current visible item the item with the given key
      *  will be kept as the first visible one.
-     * @param contentType a factory of content types representing the item. Content for item of
+     * @param contentType a content type representing the item. Content for item of
      *  the same type can be reused more efficiently. null is a valid type as well and items
      *  of such type will be considered compatible.
+     * @param span a custom span for this item. Spans configure how many lanes defined by
+     *  [StaggeredGridCells] the item will occupy. By default each item will take one lane.
      * @param content composable content displayed by current item
      */
     @ExperimentalFoundationApi
     fun item(
         key: Any? = null,
         contentType: Any? = null,
+        span: StaggeredGridItemSpan? = null,
         content: @Composable LazyStaggeredGridItemScope.() -> Unit
     )
 
@@ -249,12 +252,15 @@
      * @param contentType a factory of content types representing the item. Content for item of
      *  the same type can be reused more efficiently. null is a valid type as well and items
      *  of such type will be considered compatible.
+     *  @param span a factory of custom spans for this item. Spans configure how many lanes defined
+     *  by [StaggeredGridCells] the item will occupy. By default each item will take one lane.
      * @param itemContent composable content displayed by item on provided position
      */
     fun items(
         count: Int,
         key: ((index: Int) -> Any)? = null,
         contentType: (index: Int) -> Any? = { null },
+        span: ((index: Int) -> StaggeredGridItemSpan)? = null,
         itemContent: @Composable LazyStaggeredGridItemScope.(index: Int) -> Unit
     )
 }
@@ -274,14 +280,17 @@
  * @param contentType a factory of content types representing the item. Content for item of
  *  the same type can be reused more efficiently. null is a valid type as well and items
  *  of such type will be considered compatible.
+ * @param span a factory of custom spans for this item. Spans configure how many lanes defined
+ *  by [StaggeredGridCells] the item will occupy. By default each item will take one lane.
  * @param itemContent composable content displayed by the provided item
  */
 @ExperimentalFoundationApi
-fun <T> LazyStaggeredGridScope.items(
+inline fun <T> LazyStaggeredGridScope.items(
     items: List<T>,
-    key: ((item: T) -> Any)? = null,
-    contentType: (item: T) -> Any? = { null },
-    itemContent: @Composable LazyStaggeredGridItemScope.(item: T) -> Unit
+    noinline key: ((item: T) -> Any)? = null,
+    crossinline contentType: (item: T) -> Any? = { null },
+    noinline span: ((item: T) -> StaggeredGridItemSpan)? = null,
+    crossinline itemContent: @Composable LazyStaggeredGridItemScope.(item: T) -> Unit
 ) {
     items(
         count = items.size,
@@ -289,6 +298,9 @@
             { index -> key(items[index]) }
         },
         contentType = { index -> contentType(items[index]) },
+        span = span?.let {
+            { index -> span(items[index]) }
+        },
         itemContent = { index -> itemContent(items[index]) }
     )
 }
@@ -308,14 +320,17 @@
  * @param contentType a factory of content types representing the item. Content for item of
  *  the same type can be reused more efficiently. null is a valid type as well and items
  *  of such type will be considered compatible.
+ * @param span a factory of custom spans for this item. Spans configure how many lanes defined
+ *  by [StaggeredGridCells] the item will occupy. By default each item will take one lane.
  * @param itemContent composable content displayed given item and index
  */
 @ExperimentalFoundationApi
-fun <T> LazyStaggeredGridScope.itemsIndexed(
+inline fun <T> LazyStaggeredGridScope.itemsIndexed(
     items: List<T>,
-    key: ((index: Int, item: T) -> Any)? = null,
-    contentType: (index: Int, item: T) -> Any? = { _, _ -> null },
-    itemContent: @Composable LazyStaggeredGridItemScope.(index: Int, item: T) -> Unit
+    noinline key: ((index: Int, item: T) -> Any)? = null,
+    crossinline contentType: (index: Int, item: T) -> Any? = { _, _ -> null },
+    noinline span: ((index: Int, item: T) -> StaggeredGridItemSpan)? = null,
+    crossinline itemContent: @Composable LazyStaggeredGridItemScope.(index: Int, item: T) -> Unit
 ) {
     items(
         count = items.size,
@@ -323,6 +338,9 @@
             { index -> key(index, items[index]) }
         },
         contentType = { index -> contentType(index, items[index]) },
+        span = span?.let {
+            { index -> span(index, items[index]) }
+        },
         itemContent = { index -> itemContent(index, items[index]) }
     )
 }
@@ -342,14 +360,17 @@
  * @param contentType a factory of content types representing the item. Content for item of
  *  the same type can be reused more efficiently. null is a valid type as well and items
  *  of such type will be considered compatible.
+ * @param span a factory of custom spans for this item. Spans configure how many lanes defined
+ *  by [StaggeredGridCells] the item will occupy. By default each item will take one lane.
  * @param itemContent composable content displayed by the provided item
  */
 @ExperimentalFoundationApi
-fun <T> LazyStaggeredGridScope.items(
+inline fun <T> LazyStaggeredGridScope.items(
     items: Array<T>,
-    key: ((item: T) -> Any)? = null,
-    contentType: (item: T) -> Any? = { null },
-    itemContent: @Composable LazyStaggeredGridItemScope.(item: T) -> Unit
+    noinline key: ((item: T) -> Any)? = null,
+    crossinline contentType: (item: T) -> Any? = { null },
+    noinline span: ((item: T) -> StaggeredGridItemSpan)? = null,
+    crossinline itemContent: @Composable LazyStaggeredGridItemScope.(item: T) -> Unit
 ) {
     items(
         count = items.size,
@@ -357,6 +378,9 @@
             { index -> key(items[index]) }
         },
         contentType = { index -> contentType(items[index]) },
+        span = span?.let {
+            { index -> span(items[index]) }
+        },
         itemContent = { index -> itemContent(items[index]) }
     )
 }
@@ -376,14 +400,17 @@
  * @param contentType a factory of content types representing the item. Content for item of
  *  the same type can be reused more efficiently. null is a valid type as well and items
  *  of such type will be considered compatible.
+ * @param span a factory of custom spans for this item. Spans configure how many lanes defined
+ *  by [StaggeredGridCells] the item will occupy. By default each item will take one lane.
  * @param itemContent composable content displayed given item and index
  */
 @ExperimentalFoundationApi
-fun <T> LazyStaggeredGridScope.itemsIndexed(
+inline fun <T> LazyStaggeredGridScope.itemsIndexed(
     items: Array<T>,
-    key: ((index: Int, item: T) -> Any)? = null,
-    contentType: (index: Int, item: T) -> Any? = { _, _ -> null },
-    itemContent: @Composable LazyStaggeredGridItemScope.(index: Int, item: T) -> Unit
+    noinline key: ((index: Int, item: T) -> Any)? = null,
+    crossinline contentType: (index: Int, item: T) -> Any? = { _, _ -> null },
+    noinline span: ((index: Int, item: T) -> StaggeredGridItemSpan)? = null,
+    crossinline itemContent: @Composable LazyStaggeredGridItemScope.(index: Int, item: T) -> Unit
 ) {
     items(
         count = items.size,
@@ -391,6 +418,9 @@
             { index -> key(index, items[index]) }
         },
         contentType = { index -> contentType(index, items[index]) },
+        span = span?.let {
+            { index -> span(index, items[index]) }
+        },
         itemContent = { index -> itemContent(index, items[index]) }
     )
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridItemProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridItemProvider.kt
index 5d35898..be07e927 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridItemProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridItemProvider.kt
@@ -25,12 +25,17 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberUpdatedState
 
+@OptIn(ExperimentalFoundationApi::class)
+internal interface LazyStaggeredGridItemProvider : LazyLayoutItemProvider {
+    val spanProvider: LazyStaggeredGridSpanProvider
+}
+
 @Composable
 @ExperimentalFoundationApi
 internal fun rememberStaggeredGridItemProvider(
     state: LazyStaggeredGridState,
     content: LazyStaggeredGridScope.() -> Unit,
-): LazyLayoutItemProvider {
+): LazyStaggeredGridItemProvider {
     val latestContent = rememberUpdatedState(content)
     val nearestItemsRangeState = rememberLazyNearestItemsRangeState(
         firstVisibleItemIndex = { state.firstVisibleItemIndex },
@@ -40,16 +45,24 @@
     return remember(state) {
         val itemProviderState = derivedStateOf {
             val scope = LazyStaggeredGridScopeImpl().apply(latestContent.value)
-            LazyLayoutItemProvider(
+            object : LazyLayoutItemProvider by LazyLayoutItemProvider(
                 scope.intervals,
                 nearestItemsRangeState.value,
-            ) { interval, index ->
-                interval.value.item.invoke(
-                    LazyStaggeredGridItemScopeImpl,
-                    index - interval.startIndex
-                )
+                { interval, index ->
+                    interval.value.item.invoke(
+                        LazyStaggeredGridItemScopeImpl,
+                        index - interval.startIndex
+                    )
+                }
+            ), LazyStaggeredGridItemProvider {
+                override val spanProvider = LazyStaggeredGridSpanProvider(scope.intervals)
             }
         }
-        object : LazyLayoutItemProvider by DelegatingLazyLayoutItemProvider(itemProviderState) { }
+
+        object : LazyLayoutItemProvider by DelegatingLazyLayoutItemProvider(itemProviderState),
+            LazyStaggeredGridItemProvider {
+
+            override val spanProvider get() = itemProviderState.value.spanProvider
+        }
     }
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLaneInfo.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLaneInfo.kt
new file mode 100644
index 0000000..eee96e9
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridLaneInfo.kt
@@ -0,0 +1,207 @@
+/*
+ * 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.compose.foundation.lazy.staggeredgrid
+
+/**
+ * Utility class to remember grid lane assignments in a sliding window relative to requested
+ * item position (usually reflected by scroll position).
+ * Remembers the maximum range of remembered items is reflected by [MaxCapacity], if index is beyond
+ * the bounds, [anchor] moves to reflect new position.
+ */
+internal class LazyStaggeredGridLaneInfo {
+    private var anchor = 0
+    private var lanes = IntArray(16)
+    private val spannedItems = ArrayDeque<SpannedItem>()
+
+    private class SpannedItem(val index: Int, var gaps: IntArray)
+
+    /**
+     * Sets given lane for given item index.
+     */
+    fun setLane(itemIndex: Int, lane: Int) {
+        require(itemIndex >= 0) { "Negative lanes are not supported" }
+        ensureValidIndex(itemIndex)
+        lanes[itemIndex - anchor] = lane + 1
+    }
+
+    /**
+     * Get lane for given item index.
+     * @return lane previously recorded for given item or [Unset] if it doesn't exist.
+     */
+    fun getLane(itemIndex: Int): Int {
+        if (itemIndex < lowerBound() || itemIndex >= upperBound()) {
+            return Unset
+        }
+        return lanes[itemIndex - anchor] - 1
+    }
+
+    /**
+     * Checks whether item can be in the target lane
+     * @param itemIndex item to check lane for
+     * @param targetLane lane it should belong to
+     */
+    fun assignedToLane(itemIndex: Int, targetLane: Int): Boolean {
+        val lane = getLane(itemIndex)
+        return lane == targetLane || lane == Unset || lane == FullSpan
+    }
+
+    /**
+     * @return upper bound of currently valid item range
+     */
+    /* @VisibleForTests */
+    fun upperBound(): Int = anchor + lanes.size
+
+    /**
+     * @return lower bound of currently valid item range
+     */
+    /* @VisibleForTests */
+    fun lowerBound(): Int = anchor
+
+    /**
+     * Delete remembered lane assignments.
+     */
+    fun reset() {
+        lanes.fill(0)
+        spannedItems.clear()
+    }
+
+    /**
+     * Find the previous item relative to [itemIndex] set to target lane
+     * @return found item index or -1 if it doesn't exist.
+     */
+    fun findPreviousItemIndex(itemIndex: Int, targetLane: Int): Int {
+        for (i in (itemIndex - 1) downTo 0) {
+            if (assignedToLane(i, targetLane)) {
+                return i
+            }
+        }
+        return -1
+    }
+
+    /**
+     * Find the next item relative to [itemIndex] set to target lane
+     * @return found item index or [upperBound] if it doesn't exist.
+     */
+    fun findNextItemIndex(itemIndex: Int, targetLane: Int): Int {
+        for (i in itemIndex + 1 until upperBound()) {
+            if (assignedToLane(i, targetLane)) {
+                return i
+            }
+        }
+        return upperBound()
+    }
+
+    fun ensureValidIndex(requestedIndex: Int) {
+        val requestedCapacity = requestedIndex - anchor
+
+        if (requestedCapacity in 0 until MaxCapacity) {
+            // simplest path - just grow array to given capacity
+            ensureCapacity(requestedCapacity + 1)
+        } else {
+            // requested index is beyond current span bounds
+            // rebase anchor so that requested index is in the middle of span array
+            val oldAnchor = anchor
+            anchor = maxOf(requestedIndex - (lanes.size / 2), 0)
+            var delta = anchor - oldAnchor
+
+            if (delta >= 0) {
+                // copy previous span data if delta is smaller than span size
+                if (delta < lanes.size) {
+                    lanes.copyInto(
+                        lanes,
+                        destinationOffset = 0,
+                        startIndex = delta,
+                        endIndex = lanes.size
+                    )
+                }
+                // fill the rest of the spans with default values
+                lanes.fill(0, maxOf(0, lanes.size - delta), lanes.size)
+            } else {
+                delta = -delta
+                // check if we can grow spans to match delta
+                if (lanes.size + delta < MaxCapacity) {
+                    // grow spans and leave space in the start
+                    ensureCapacity(lanes.size + delta + 1, delta)
+                } else {
+                    // otherwise, just move data that fits
+                    if (delta < lanes.size) {
+                        lanes.copyInto(
+                            lanes,
+                            destinationOffset = delta,
+                            startIndex = 0,
+                            endIndex = lanes.size - delta
+                        )
+                    }
+                    // fill the rest of the spans with default values
+                    lanes.fill(0, 0, minOf(lanes.size, delta))
+                }
+            }
+        }
+
+        // ensure full item spans beyond saved index are forgotten to save memory
+
+        while (spannedItems.isNotEmpty() && spannedItems.first().index < lowerBound()) {
+            spannedItems.removeFirst()
+        }
+
+        while (spannedItems.isNotEmpty() && spannedItems.last().index > upperBound()) {
+            spannedItems.removeLast()
+        }
+    }
+
+    fun setGaps(itemIndex: Int, gaps: IntArray?) {
+        val foundIndex = spannedItems.binarySearchBy(itemIndex) { it.index }
+        if (foundIndex < 0) {
+            if (gaps == null) {
+                return
+            }
+            // not found, insert new element
+            val insertionIndex = -(foundIndex + 1)
+            spannedItems.add(insertionIndex, SpannedItem(itemIndex, gaps))
+        } else {
+            if (gaps == null) {
+                // found, but gaps are reset, remove item
+                spannedItems.removeAt(foundIndex)
+            } else {
+                // found, update gaps
+                spannedItems[foundIndex].gaps = gaps
+            }
+        }
+    }
+
+    fun getGaps(itemIndex: Int): IntArray? {
+        val foundIndex = spannedItems.binarySearchBy(itemIndex) { it.index }
+        return spannedItems.getOrNull(foundIndex)?.gaps
+    }
+
+    private fun ensureCapacity(capacity: Int, newOffset: Int = 0) {
+        require(capacity <= MaxCapacity) {
+            "Requested item capacity $capacity is larger than max supported: $MaxCapacity!"
+        }
+        if (lanes.size < capacity) {
+            var newSize = lanes.size
+            while (newSize < capacity) newSize *= 2
+            lanes = lanes.copyInto(IntArray(newSize), destinationOffset = newOffset)
+        }
+    }
+
+    companion object {
+        private const val MaxCapacity = 131_072 // Closest to 100_000, 2 ^ 17
+        internal const val Unset = -1
+        internal const val FullSpan = -2
+    }
+}
\ No newline at end of file
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 138d229..1c365bf 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
@@ -21,6 +21,8 @@
 import androidx.compose.foundation.fastMaxOfOrNull
 import androidx.compose.foundation.lazy.layout.LazyLayoutItemProvider
 import androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScope
+import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridLaneInfo.Companion.FullSpan
+import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridLaneInfo.Companion.Unset
 import androidx.compose.runtime.collection.MutableVector
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.layout.Placeable
@@ -30,15 +32,24 @@
 import androidx.compose.ui.unit.constrainHeight
 import androidx.compose.ui.unit.constrainWidth
 import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.packInts
+import androidx.compose.ui.util.unpackInt1
+import androidx.compose.ui.util.unpackInt2
 import kotlin.math.abs
-import kotlin.math.min
 import kotlin.math.roundToInt
 import kotlin.math.sign
 
+private const val DebugLoggingEnabled = false
+private inline fun debugLog(message: () -> String) {
+    if (DebugLoggingEnabled) {
+        println(message())
+    }
+}
+
 @ExperimentalFoundationApi
 internal fun LazyLayoutMeasureScope.measureStaggeredGrid(
     state: LazyStaggeredGridState,
-    itemProvider: LazyLayoutItemProvider,
+    itemProvider: LazyStaggeredGridItemProvider,
     resolvedSlotSums: IntArray,
     constraints: Constraints,
     isVertical: Boolean,
@@ -77,23 +88,23 @@
             } else {
                 // Grid got resized (or we are in a initial state)
                 // Adjust indices accordingly
-                context.spans.reset()
+                context.laneInfo.reset()
                 IntArray(resolvedSlotSums.size).apply {
                     // Try to adjust indices in case grid got resized
                     for (lane in indices) {
                         this[lane] = if (
-                            lane < firstVisibleIndices.size && firstVisibleIndices[lane] != -1
+                            lane < firstVisibleIndices.size && firstVisibleIndices[lane] != Unset
                         ) {
                             firstVisibleIndices[lane]
                         } else {
                             if (lane == 0) {
                                 0
                             } else {
-                                context.findNextItemIndex(this[lane - 1], lane)
+                                maxInRange(SpanRange(0, lane)) + 1
                             }
                         }
                         // Ensure spans are updated to be in correct range
-                        context.spans.setSpan(this[lane], lane)
+                        context.laneInfo.setLane(this[lane], lane)
                     }
                 }
             }
@@ -127,7 +138,7 @@
 @OptIn(ExperimentalFoundationApi::class)
 private class LazyStaggeredGridMeasureContext(
     val state: LazyStaggeredGridState,
-    val itemProvider: LazyLayoutItemProvider,
+    val itemProvider: LazyStaggeredGridItemProvider,
     val resolvedSlotSums: IntArray,
     val constraints: Constraints,
     val isVertical: Boolean,
@@ -143,20 +154,40 @@
         isVertical,
         itemProvider,
         measureScope,
-        resolvedSlotSums
-    ) { index, lane, key, placeables ->
-        val isLastInLane = spans.findNextItemIndex(index, lane) >= itemProvider.itemCount
+        resolvedSlotSums,
+        crossAxisSpacing
+    ) { index, lane, span, key, placeables ->
         LazyStaggeredGridMeasuredItem(
             index,
             key,
             placeables,
             isVertical,
             contentOffset,
-            if (isLastInLane) 0 else mainAxisSpacing
+            mainAxisSpacing,
+            lane,
+            span
         )
     }
 
-    val spans = state.spans
+    val laneInfo = state.laneInfo
+
+    val laneCount = resolvedSlotSums.size
+
+    fun LazyStaggeredGridItemProvider.isFullSpan(itemIndex: Int): Boolean =
+        spanProvider.isFullSpan(itemIndex)
+
+    fun LazyStaggeredGridItemProvider.getSpanRange(itemIndex: Int, lane: Int): SpanRange {
+        val isFullSpan = spanProvider.isFullSpan(itemIndex)
+        val span = if (isFullSpan) laneCount else 1
+        val targetLane = if (isFullSpan) 0 else lane
+        return SpanRange(targetLane, span)
+    }
+
+    inline val SpanRange.isFullSpan: Boolean
+        get() = end - start != 1
+
+    inline val SpanRange.laneInfo: Int
+        get() = if (isFullSpan) FullSpan else start
 }
 
 @ExperimentalFoundationApi
@@ -169,7 +200,7 @@
     with(measureScope) {
         val itemCount = itemProvider.itemCount
 
-        if (itemCount <= 0 || resolvedSlotSums.isEmpty()) {
+        if (itemCount <= 0 || laneCount == 0) {
             return LazyStaggeredGridMeasureResult(
                 firstVisibleItemIndices = initialItemIndices,
                 firstVisibleItemScrollOffsets = initialItemOffsets,
@@ -202,7 +233,7 @@
         firstItemOffsets.offsetBy(-scrollDelta)
 
         // this will contain all the MeasuredItems representing the visible items
-        val measuredItems = Array(resolvedSlotSums.size) {
+        val measuredItems = Array(laneCount) {
             ArrayDeque<LazyStaggeredGridMeasuredItem>()
         }
 
@@ -215,7 +246,7 @@
                 val itemIndex = firstItemIndices[lane]
                 val itemOffset = firstItemOffsets[lane]
 
-                if (itemOffset < -mainAxisSpacing && itemIndex > 0) {
+                if (itemOffset < maxOf(-mainAxisSpacing, 0) && itemIndex > 0) {
                     return true
                 }
             }
@@ -225,33 +256,60 @@
 
         var laneToCheckForGaps = -1
 
-        // we had scrolled backward or we compose items in the start padding area, which means
-        // items before current firstItemScrollOffset should be visible. compose them and update
-        // firstItemScrollOffset
-        while (hasSpaceBeforeFirst()) {
-            val laneIndex = firstItemOffsets.indexOfMinValue()
-            val previousItemIndex = findPreviousItemIndex(
-                item = firstItemIndices[laneIndex],
-                lane = laneIndex
-            )
+        debugLog { "=========== MEASURE START ==========" }
+        debugLog {
+            "| Filling up from indices: ${firstItemIndices.toList()}, " +
+                "offsets: ${firstItemOffsets.toList()}"
+        }
 
+        // we had scrolled backward or we compose items in the start padding area, which means
+        // items before current firstItemOffset should be visible. compose them and update
+        // firstItemOffsets
+        while (hasSpaceBeforeFirst()) {
+            // staggered grid always keeps item index increasing top to bottom
+            // the first item that should contain something before it must have the largest index
+            // among the rest
+            val laneIndex = firstItemIndices.indexOfMaxValue()
+            val itemIndex = firstItemIndices[laneIndex]
+
+            // other lanes might have smaller offsets than the one chosen above, which indicates
+            // incorrect measurement (e.g. item was deleted or it changed size)
+            // correct this by offsetting affected lane back to match currently chosen offset
+            for (i in firstItemOffsets.indices) {
+                if (
+                    firstItemIndices[i] != firstItemIndices[laneIndex] &&
+                        firstItemOffsets[i] < firstItemOffsets[laneIndex]
+                ) {
+                    // If offset of the lane is smaller than currently chosen lane,
+                    // offset the lane to be where current value of the chosen index is.
+                    firstItemOffsets[i] = firstItemOffsets[laneIndex]
+                }
+            }
+
+            val previousItemIndex = findPreviousItemIndex(itemIndex, laneIndex)
             if (previousItemIndex < 0) {
                 laneToCheckForGaps = laneIndex
                 break
             }
 
-            if (spans.getSpan(previousItemIndex) == LazyStaggeredGridSpans.Unset) {
-                spans.setSpan(previousItemIndex, laneIndex)
-            }
-
+            val spanRange = itemProvider.getSpanRange(previousItemIndex, laneIndex)
+            laneInfo.setLane(previousItemIndex, spanRange.laneInfo)
             val measuredItem = measuredItemProvider.getAndMeasure(
-                previousItemIndex,
-                laneIndex
+                index = previousItemIndex,
+                span = spanRange
             )
-            measuredItems[laneIndex].addFirst(measuredItem)
 
-            firstItemIndices[laneIndex] = previousItemIndex
-            firstItemOffsets[laneIndex] += measuredItem.sizeWithSpacings
+            val offset = firstItemOffsets.maxInRange(spanRange)
+            val gaps = if (spanRange.isFullSpan) laneInfo.getGaps(previousItemIndex) else null
+            spanRange.forEach { lane ->
+                firstItemIndices[lane] = previousItemIndex
+                val gap = if (gaps == null) 0 else gaps[lane]
+                firstItemOffsets[lane] = offset + measuredItem.sizeWithSpacings + gap
+            }
+        }
+        debugLog {
+            @Suppress("ListIterator")
+            "| up filled, measured items are ${measuredItems.map { it.map { it.index } }}"
         }
 
         fun misalignedStart(referenceLane: Int): Boolean {
@@ -261,17 +319,19 @@
 
             // Case 1: Each lane has laid out all items, but offsets do no match
             val misalignedOffsets = laneRange.any { lane ->
-                findPreviousItemIndex(firstItemIndices[lane], lane) == -1 &&
+                findPreviousItemIndex(firstItemIndices[lane], lane) == Unset &&
                     firstItemOffsets[lane] != firstItemOffsets[referenceLane]
             }
             // Case 2: Some lanes are still missing items, and there's no space left to place them
             val moreItemsInOtherLanes = laneRange.any { lane ->
-                findPreviousItemIndex(firstItemIndices[lane], lane) != -1 &&
+                findPreviousItemIndex(firstItemIndices[lane], lane) != Unset &&
                     firstItemOffsets[lane] >= firstItemOffsets[referenceLane]
             }
             // Case 3: the first item is in the wrong lane (it should always be in
             // the first one)
-            val firstItemInWrongLane = spans.getSpan(0) != 0
+            val firstItemLane = laneInfo.getLane(0)
+            val firstItemInWrongLane =
+                firstItemLane != 0 && firstItemLane != Unset && firstItemLane != FullSpan
             // If items are not aligned, reset all measurement data we gathered before and
             // proceed with initial measure
             return misalignedOffsets || moreItemsInOtherLanes || firstItemInWrongLane
@@ -285,6 +345,9 @@
         if (firstItemOffsets[0] < minOffset) {
             scrollDelta += firstItemOffsets[0]
             firstItemOffsets.offsetBy(minOffset - firstItemOffsets[0])
+            debugLog {
+                "| correcting scroll delta from ${firstItemOffsets[0]} to $minOffset"
+            }
         }
 
         // neutralize previously added start padding as we stopped filling the before content padding
@@ -297,7 +360,7 @@
         if (laneToCheckForGaps != -1) {
             val lane = laneToCheckForGaps
             if (misalignedStart(lane) && canRestartMeasure) {
-                spans.reset()
+                laneInfo.reset()
                 return measure(
                     initialScrollDelta = scrollDelta,
                     initialItemIndices = IntArray(firstItemIndices.size) { -1 },
@@ -309,29 +372,73 @@
             }
         }
 
-        val currentItemIndices = initialItemIndices.copyOf().apply {
-            // ensure indices match item count, in case it decreased
-            ensureIndicesInRange(this, itemCount)
-        }
-        val currentItemOffsets = IntArray(initialItemOffsets.size) {
-            -(initialItemOffsets[it] - scrollDelta)
+        // start measuring down from first item indices/offsets decided above to ensure correct
+        // arrangement.
+        // this means we are calling measure second time on items previously measured in this
+        // function, but LazyLayout caches them, so no overhead.
+        val currentItemIndices = firstItemIndices.copyOf()
+        val currentItemOffsets = IntArray(firstItemOffsets.size) {
+            -firstItemOffsets[it]
         }
 
         val maxOffset = (mainAxisAvailableSize + afterContentPadding).coerceAtLeast(0)
 
-        // compose first visible items we received from state
-        currentItemIndices.forEachIndexed { laneIndex, itemIndex ->
-            if (itemIndex < 0) return@forEachIndexed
+        debugLog {
+            "| filling from current: indices: ${currentItemIndices.toList()}, " +
+                "offsets: ${currentItemOffsets.toList()}"
+        }
 
-            val measuredItem = measuredItemProvider.getAndMeasure(itemIndex, laneIndex)
-            currentItemOffsets[laneIndex] += measuredItem.sizeWithSpacings
-            measuredItems[laneIndex].addLast(measuredItem)
+        // current item should be pointing to the index of previously measured item below,
+        // as lane assignments must be decided based on size and offset of all previous items
+        // this loop makes sure to measure items that were initially passed to the current item
+        // indices with correct item order
+        var initialItemsMeasured = 0
+        var initialLaneToMeasure = currentItemIndices.indexOfMinValue()
+        while (initialLaneToMeasure != -1 && initialItemsMeasured < laneCount) {
+            val itemIndex = currentItemIndices[initialLaneToMeasure]
+            val laneIndex = initialLaneToMeasure
 
-            spans.setSpan(itemIndex, laneIndex)
+            initialLaneToMeasure = currentItemIndices.indexOfMinValue(minBound = itemIndex)
+            initialItemsMeasured++
+
+            if (itemIndex < 0) continue
+
+            val spanRange = itemProvider.getSpanRange(itemIndex, laneIndex)
+            val measuredItem = measuredItemProvider.getAndMeasure(
+                itemIndex,
+                spanRange
+            )
+
+            laneInfo.setLane(itemIndex, spanRange.laneInfo)
+            val offset = currentItemOffsets.maxInRange(spanRange) + measuredItem.sizeWithSpacings
+            spanRange.forEach { lane ->
+                currentItemOffsets[lane] = offset
+                currentItemIndices[lane] = itemIndex
+                measuredItems[lane].addLast(measuredItem)
+            }
+
+            if (currentItemOffsets[spanRange.start] <= minOffset + mainAxisSpacing) {
+                measuredItem.isVisible = false
+            }
+
+            if (spanRange.isFullSpan) {
+                // full span items overwrite other slots if we measure it here, so skip measuring
+                // the rest of the slots
+                initialItemsMeasured = laneCount
+            }
+        }
+
+        debugLog {
+            @Suppress("ListIterator")
+            "| current filled, measured items are ${measuredItems.map { it.map { it.index } }}"
+        }
+        debugLog {
+            "| filling down from indices: ${currentItemIndices.toList()}, " +
+                "offsets: ${currentItemOffsets.toList()}"
         }
 
         // then composing visible items forward until we fill the whole viewport.
-        // we want to have at least one item in visibleItems even if in fact all the items are
+        // we want to have at least one item in measuredItems even if in fact all the items are
         // offscreen, this can happen if the content padding is larger than the available size.
         while (
             currentItemOffsets.any {
@@ -340,74 +447,72 @@
             } || measuredItems.all { it.isEmpty() }
         ) {
             val currentLaneIndex = currentItemOffsets.indexOfMinValue()
-            val nextItemIndex =
-                findNextItemIndex(currentItemIndices[currentLaneIndex], currentLaneIndex)
+            val previousItemIndex = currentItemIndices.max()
+            val itemIndex = previousItemIndex + 1
 
-            if (nextItemIndex >= itemCount) {
-                // if any items changed its size, the spans may not behave correctly
-                // there are no more items in this lane, but there could be more in others
-                // recheck if we can add more items and reset spans accordingly
-                var missedItemIndex = Int.MAX_VALUE
-                currentItemIndices.forEachIndexed { laneIndex, i ->
-                    if (laneIndex == currentLaneIndex) return@forEachIndexed
-                    var itemIndex = findNextItemIndex(i, laneIndex)
-                    while (itemIndex < itemCount) {
-                        missedItemIndex = minOf(itemIndex, missedItemIndex)
-                        spans.setSpan(itemIndex, LazyStaggeredGridSpans.Unset)
-                        itemIndex = findNextItemIndex(itemIndex, laneIndex)
-                    }
-                }
-                // there's at least one missed item which may fit current lane
-                if (missedItemIndex != Int.MAX_VALUE && canRestartMeasure) {
-                    // reset current lane to the missed item index and restart measure
-                    initialItemIndices[currentLaneIndex] =
-                        min(initialItemIndices[currentLaneIndex], missedItemIndex)
-                    return measure(
-                        initialScrollDelta = initialScrollDelta,
-                        initialItemIndices = initialItemIndices,
-                        initialItemOffsets = initialItemOffsets,
-                        canRestartMeasure = false
-                    )
-                } else {
-                    break
-                }
+            if (itemIndex >= itemCount) {
+                break
             }
 
-            if (firstItemIndices[currentLaneIndex] == -1) {
-                firstItemIndices[currentLaneIndex] = nextItemIndex
-            }
-            spans.setSpan(nextItemIndex, currentLaneIndex)
+            val spanRange = itemProvider.getSpanRange(itemIndex, currentLaneIndex)
 
-            val measuredItem =
-                measuredItemProvider.getAndMeasure(nextItemIndex, currentLaneIndex)
-            currentItemOffsets[currentLaneIndex] += measuredItem.sizeWithSpacings
-            measuredItems[currentLaneIndex].addLast(measuredItem)
-            currentItemIndices[currentLaneIndex] = nextItemIndex
+            laneInfo.setLane(itemIndex, spanRange.laneInfo)
+            val measuredItem = measuredItemProvider.getAndMeasure(itemIndex, spanRange)
+
+            val offset = currentItemOffsets.maxInRange(spanRange)
+            val gaps = if (spanRange.isFullSpan) {
+                laneInfo.getGaps(itemIndex) ?: IntArray(laneCount)
+            } else {
+                null
+            }
+            spanRange.forEach { lane ->
+                if (gaps != null) {
+                    gaps[lane] = offset - currentItemOffsets[lane]
+                }
+                currentItemIndices[lane] = itemIndex
+                currentItemOffsets[lane] = offset + measuredItem.sizeWithSpacings
+                measuredItems[lane].addLast(measuredItem)
+            }
+            laneInfo.setGaps(itemIndex, gaps)
+
+            if (currentItemOffsets[spanRange.start] <= minOffset + mainAxisSpacing) {
+                // We scrolled past measuredItem, and it is not visible anymore. We measured it
+                // for correct positioning of other items, but there's no need to place it.
+                // Mark it as not visible and filter below.
+                measuredItem.isVisible = false
+            }
+        }
+
+        debugLog {
+            @Suppress("ListIterator")
+            "| down filled, measured items are ${measuredItems.map { it.map { it.index } }}"
         }
 
         // some measured items are offscreen, remove them from the list and adjust indices/offsets
         for (laneIndex in measuredItems.indices) {
             val laneItems = measuredItems[laneIndex]
-            var offset = currentItemOffsets[laneIndex]
-            var inBoundsIndex = 0
-            for (i in laneItems.lastIndex downTo 0) {
-                val item = laneItems[i]
-                offset -= item.sizeWithSpacings
-                inBoundsIndex = i
-                if (offset <= minOffset + mainAxisSpacing) {
-                    break
-                }
+
+            while (laneItems.size > 1 && !laneItems.first().isVisible) {
+                val item = laneItems.removeFirst()
+                val gaps = if (item.span != 1) laneInfo.getGaps(item.index) else null
+                firstItemOffsets[laneIndex] -=
+                    item.sizeWithSpacings + if (gaps == null) 0 else gaps[laneIndex]
             }
 
-            // the rest of the items are offscreen, update firstIndex/Offset for lane and remove
-            // items from measured list
-            for (i in 0 until inBoundsIndex) {
-                val item = laneItems.removeFirst()
-                firstItemOffsets[laneIndex] -= item.sizeWithSpacings
-            }
-            if (laneItems.isNotEmpty()) {
-                firstItemIndices[laneIndex] = laneItems.first().index
-            }
+            firstItemIndices[laneIndex] = laneItems.firstOrNull()?.index ?: Unset
+        }
+
+        if (currentItemIndices.any { it == itemCount - 1 }) {
+            currentItemOffsets.offsetBy(-mainAxisSpacing)
+        }
+
+        debugLog {
+            @Suppress("ListIterator")
+            "| removed invisible items: ${measuredItems.map { it.map { it.index } }}"
+        }
+        debugLog {
+            "| filling back up from indices: ${firstItemIndices.toList()}, " +
+                "offsets: ${firstItemOffsets.toList()}"
         }
 
         // we didn't fill the whole viewport with items starting from firstVisibleItemIndex.
@@ -433,7 +538,7 @@
 
                 if (previousIndex < 0) {
                     if (misalignedStart(laneIndex) && canRestartMeasure) {
-                        spans.reset()
+                        laneInfo.reset()
                         return measure(
                             initialScrollDelta = scrollDelta,
                             initialItemIndices = IntArray(firstItemIndices.size) { -1 },
@@ -446,15 +551,21 @@
                     break
                 }
 
-                spans.setSpan(previousIndex, laneIndex)
-
+                val spanRange = itemProvider.getSpanRange(previousIndex, laneIndex)
+                laneInfo.setLane(previousIndex, spanRange.laneInfo)
                 val measuredItem = measuredItemProvider.getAndMeasure(
-                    previousIndex,
-                    laneIndex
+                    index = previousIndex,
+                    spanRange
                 )
-                measuredItems[laneIndex].addFirst(measuredItem)
-                firstItemOffsets[laneIndex] += measuredItem.sizeWithSpacings
-                firstItemIndices[laneIndex] = previousIndex
+
+                val offset = firstItemOffsets.maxInRange(spanRange)
+                val gaps = if (spanRange.isFullSpan) laneInfo.getGaps(previousIndex) else null
+                spanRange.forEach { lane ->
+                    measuredItems[lane].addFirst(measuredItem)
+                    firstItemIndices[lane] = previousIndex
+                    val gap = if (gaps == null) 0 else gaps[lane]
+                    firstItemOffsets[lane] = offset + measuredItem.sizeWithSpacings + gap
+                }
             }
             scrollDelta += toScrollBack
 
@@ -467,6 +578,14 @@
             }
         }
 
+        debugLog {
+            @Suppress("ListIterator")
+            "| measured: ${measuredItems.map { it.map { it.index } }}"
+        }
+        debugLog {
+            "| first indices: ${firstItemIndices.toList()}, offsets: ${firstItemOffsets.toList()}"
+        }
+
         // report the amount of pixels we consumed. scrollDelta can be smaller than
         // scrollToBeConsumed if there were not enough items to fill the offered space or it
         // can be larger if items were resized, or if, for example, we were previously
@@ -487,12 +606,15 @@
             for (laneIndex in measuredItems.indices) {
                 val laneItems = measuredItems[laneIndex]
                 for (i in laneItems.indices) {
-                    val size = laneItems[i].sizeWithSpacings
+                    val item = laneItems[i]
+                    val gaps = laneInfo.getGaps(item.index)
+                    val size = item.sizeWithSpacings + if (gaps == null) 0 else gaps[laneIndex]
                     if (
                         i != laneItems.lastIndex &&
                         firstItemOffsets[laneIndex] != 0 &&
                         firstItemOffsets[laneIndex] >= size
                     ) {
+
                         firstItemOffsets[laneIndex] -= size
                         firstItemIndices[laneIndex] = laneItems[i + 1].index
                     } else {
@@ -502,6 +624,11 @@
             }
         }
 
+        debugLog {
+            "| final first indices: ${firstItemIndices.toList()}, " +
+                "offsets: ${firstItemOffsets.toList()}"
+        }
+
         // end measure
 
         val layoutWidth = if (isVertical) {
@@ -526,8 +653,13 @@
             }
             val item = measuredItems[laneIndex].removeFirst()
 
+            if (item.lane != laneIndex) {
+                continue
+            }
+
             // todo(b/182882362): arrangement support
-            val mainAxisOffset = itemScrollOffsets[laneIndex]
+            val spanRange = SpanRange(item.lane, item.span)
+            val mainAxisOffset = itemScrollOffsets.maxInRange(spanRange)
             val crossAxisOffset =
                 if (laneIndex == 0) {
                     0
@@ -535,8 +667,22 @@
                     resolvedSlotSums[laneIndex - 1] + crossAxisSpacing * laneIndex
                 }
 
+            if (item.placeables.isEmpty()) {
+                // nothing to place, ignore spacings
+                continue
+            }
+
             positionedItems += item.position(laneIndex, mainAxisOffset, crossAxisOffset)
-            itemScrollOffsets[laneIndex] += item.sizeWithSpacings
+            spanRange.forEach { lane ->
+                itemScrollOffsets[lane] = mainAxisOffset + item.sizeWithSpacings
+            }
+        }
+
+        debugLog {
+            "| positioned: ${positionedItems.map { "${it.index} at ${it.offset}" }.toList()}"
+        }
+        debugLog {
+            "========== MEASURE DONE ==========="
         }
 
         // todo: reverse layout support
@@ -573,17 +719,40 @@
     }
 }
 
+@JvmInline
+private value class SpanRange private constructor(val packedValue: Long) {
+    constructor(lane: Int, span: Int) : this(packInts(lane, lane + span))
+
+    inline val start get(): Int = unpackInt1(packedValue)
+    inline val end get(): Int = unpackInt2(packedValue)
+    inline val size get(): Int = end - start
+}
+
+private inline fun SpanRange.forEach(block: (Int) -> Unit) {
+    for (i in start until end) {
+        block(i)
+    }
+}
+
 private fun IntArray.offsetBy(delta: Int) {
     for (i in indices) {
         this[i] = this[i] + delta
     }
 }
 
-internal fun IntArray.indexOfMinValue(): Int {
+private fun IntArray.maxInRange(indexRange: SpanRange): Int {
+    var max = Int.MIN_VALUE
+    indexRange.forEach {
+        max = maxOf(max, this[it])
+    }
+    return max
+}
+
+internal fun IntArray.indexOfMinValue(minBound: Int = Int.MIN_VALUE): Int {
     var result = -1
     var min = Int.MAX_VALUE
     for (i in indices) {
-        if (min > this[i]) {
+        if (this[i] in (minBound + 1) until min) {
             min = this[i]
             result = i
         }
@@ -632,21 +801,20 @@
 ) {
     // reverse traverse to make sure last items are recorded to the latter lanes
     for (i in indices.indices.reversed()) {
-        while (indices[i] >= itemCount) {
+        while (indices[i] >= itemCount || !laneInfo.assignedToLane(indices[i], i)) {
             indices[i] = findPreviousItemIndex(indices[i], i)
         }
-        if (indices[i] != -1) {
+        if (indices[i] >= 0) {
             // reserve item for span
-            spans.setSpan(indices[i], i)
+            if (!itemProvider.isFullSpan(indices[i])) {
+                laneInfo.setLane(indices[i], i)
+            }
         }
     }
 }
 
 private fun LazyStaggeredGridMeasureContext.findPreviousItemIndex(item: Int, lane: Int): Int =
-    spans.findPreviousItemIndex(item, lane)
-
-private fun LazyStaggeredGridMeasureContext.findNextItemIndex(item: Int, lane: Int): Int =
-    spans.findNextItemIndex(item, lane)
+    laneInfo.findPreviousItemIndex(item, lane)
 
 @OptIn(ExperimentalFoundationApi::class)
 private class LazyStaggeredGridMeasureProvider(
@@ -654,11 +822,13 @@
     private val itemProvider: LazyLayoutItemProvider,
     private val measureScope: LazyLayoutMeasureScope,
     private val resolvedSlotSums: IntArray,
-    private val measuredItemFactory: MeasuredItemFactory
+    private val crossAxisSpacing: Int,
+    private val measuredItemFactory: MeasuredItemFactory,
 ) {
-    private fun childConstraints(slot: Int): Constraints {
+    private fun childConstraints(slot: Int, span: Int): Constraints {
         val previousSum = if (slot == 0) 0 else resolvedSlotSums[slot - 1]
-        val crossAxisSize = resolvedSlotSums[slot] - previousSum
+        val crossAxisSize =
+            resolvedSlotSums[slot + span - 1] - previousSum + crossAxisSpacing * (span - 1)
         return if (isVertical) {
             Constraints.fixedWidth(crossAxisSize)
         } else {
@@ -666,10 +836,10 @@
         }
     }
 
-    fun getAndMeasure(index: Int, lane: Int): LazyStaggeredGridMeasuredItem {
+    fun getAndMeasure(index: Int, span: SpanRange): LazyStaggeredGridMeasuredItem {
         val key = itemProvider.getKey(index)
-        val placeables = measureScope.measure(index, childConstraints(lane))
-        return measuredItemFactory.createItem(index, lane, key, placeables)
+        val placeables = measureScope.measure(index, childConstraints(span.start, span.size))
+        return measuredItemFactory.createItem(index, span.start, span.size, key, placeables)
     }
 }
 
@@ -678,6 +848,7 @@
     fun createItem(
         index: Int,
         lane: Int,
+        span: Int,
         key: Any,
         placeables: List<Placeable>
     ): LazyStaggeredGridMeasuredItem
@@ -689,8 +860,12 @@
     val placeables: List<Placeable>,
     val isVertical: Boolean,
     val contentOffset: IntOffset,
-    val spacing: Int
+    val spacing: Int,
+    val lane: Int,
+    val span: Int
 ) {
+    var isVisible = true
+
     val mainAxisSize: Int = placeables.fastFold(0) { size, placeable ->
         size + if (isVertical) placeable.height else placeable.width
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasurePolicy.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasurePolicy.kt
index a645632..29ec864 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasurePolicy.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasurePolicy.kt
@@ -23,7 +23,6 @@
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.calculateEndPadding
 import androidx.compose.foundation.layout.calculateStartPadding
-import androidx.compose.foundation.lazy.layout.LazyLayoutItemProvider
 import androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScope
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
@@ -39,7 +38,7 @@
 @ExperimentalFoundationApi
 internal fun rememberStaggeredGridMeasurePolicy(
     state: LazyStaggeredGridState,
-    itemProvider: LazyLayoutItemProvider,
+    itemProvider: LazyStaggeredGridItemProvider,
     contentPadding: PaddingValues,
     reverseLayout: Boolean,
     orientation: Orientation,
@@ -67,6 +66,7 @@
         // setup information for prefetch
         state.laneWidthsPrefixSum = resolvedSlotSums
         state.isVertical = isVertical
+        state.spanProvider = itemProvider.spanProvider
 
         val beforeContentPadding = contentPadding.beforePadding(
             orientation, reverseLayout, layoutDirection
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScope.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScope.kt
index 4d748da..364b075 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScope.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridScope.kt
@@ -21,20 +21,21 @@
 import androidx.compose.foundation.lazy.layout.MutableIntervalList
 import androidx.compose.runtime.Composable
 
-@ExperimentalFoundationApi
+@OptIn(ExperimentalFoundationApi::class)
 internal class LazyStaggeredGridScopeImpl : LazyStaggeredGridScope {
     val intervals = MutableIntervalList<LazyStaggeredGridIntervalContent>()
 
-    @ExperimentalFoundationApi
     override fun item(
         key: Any?,
         contentType: Any?,
+        span: StaggeredGridItemSpan?,
         content: @Composable LazyStaggeredGridItemScope.() -> Unit
     ) {
         items(
             count = 1,
             key = key?.let { { key } },
             contentType = { contentType },
+            span = span?.let { { span } },
             itemContent = { content() }
         )
     }
@@ -43,6 +44,7 @@
         count: Int,
         key: ((index: Int) -> Any)?,
         contentType: (index: Int) -> Any?,
+        span: ((index: Int) -> StaggeredGridItemSpan)?,
         itemContent: @Composable LazyStaggeredGridItemScope.(index: Int) -> Unit
     ) {
         intervals.addInterval(
@@ -50,6 +52,7 @@
             LazyStaggeredGridIntervalContent(
                 key,
                 contentType,
+                span,
                 itemContent
             )
         )
@@ -63,5 +66,6 @@
 internal class LazyStaggeredGridIntervalContent(
     override val key: ((index: Int) -> Any)?,
     override val type: ((index: Int) -> Any?),
+    val span: ((index: Int) -> StaggeredGridItemSpan)?,
     val item: @Composable LazyStaggeredGridItemScope.(Int) -> Unit
 ) : LazyLayoutIntervalContent
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSpan.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSpan.kt
new file mode 100644
index 0000000..1379767a
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSpan.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.compose.foundation.lazy.staggeredgrid
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.lazy.layout.IntervalList
+import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan.Companion.FullLine
+
+/**
+ * Span defines a number of lanes (columns in vertical grid/rows in horizontal grid) for
+ * staggered grid items.
+ * Two variations of span are supported:
+ *   - item taking a single lane ([SingleLane]);
+ *   - item all lanes in line ([FullLine]).
+ * By default, staggered grid uses [SingleLane] for all items.
+ */
+@ExperimentalFoundationApi
+class StaggeredGridItemSpan private constructor(internal val value: Int) {
+    companion object {
+        /**
+         * Force item to occupy whole line in cross axis.
+         */
+        val FullLine = StaggeredGridItemSpan(0)
+
+        /**
+         * Force item to use a single lane.
+         */
+        val SingleLane = StaggeredGridItemSpan(1)
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+internal class LazyStaggeredGridSpanProvider(
+    val intervals: IntervalList<LazyStaggeredGridIntervalContent>
+) {
+    fun isFullSpan(itemIndex: Int): Boolean {
+        if (itemIndex !in 0 until intervals.size) return false
+        intervals[itemIndex].run {
+            val span = value.span
+            val localIndex = itemIndex - startIndex
+
+            return span != null && span(localIndex) === FullLine
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSpans.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSpans.kt
deleted file mode 100644
index 5ec8708..0000000
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridSpans.kt
+++ /dev/null
@@ -1,159 +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.compose.foundation.lazy.staggeredgrid
-
-/**
- * Utility class to remember grid lane assignments (spans) in a sliding window relative to requested
- * item position (usually reflected by scroll position).
- * Remembers the maximum range of remembered items is reflected by [MaxCapacity], if index is beyond
- * the bounds, [anchor] moves to reflect new position.
- */
-internal class LazyStaggeredGridSpans {
-    private var anchor = 0
-    private var spans = IntArray(16)
-
-    /**
-     * Sets given span for given item index.
-     */
-    fun setSpan(item: Int, span: Int) {
-        require(item >= 0) { "Negative spans are not supported" }
-        ensureValidIndex(item)
-        spans[item - anchor] = span + 1
-    }
-
-    /**
-     * Get span for given item index.
-     * @return span previously recorded for given item or [Unset] if it doesn't exist.
-     */
-    fun getSpan(item: Int): Int {
-        if (item < lowerBound() || item >= upperBound()) {
-            return Unset
-        }
-        return spans[item - anchor] - 1
-    }
-
-    /**
-     * @return upper bound of currently valid span range
-     */
-    /* @VisibleForTests */
-    fun upperBound(): Int = anchor + spans.size
-
-    /**
-     * @return lower bound of currently valid span range
-     */
-    /* @VisibleForTests */
-    fun lowerBound(): Int = anchor
-
-    /**
-     * Delete remembered span assignments.
-     */
-    fun reset() {
-        spans.fill(0)
-    }
-
-    /**
-     * Find the previous item relative to [item] set to target span
-     * @return found item index or -1 if it doesn't exist.
-     */
-    fun findPreviousItemIndex(item: Int, target: Int): Int {
-        for (i in (item - 1) downTo 0) {
-            val span = getSpan(i)
-            if (span == target || span == Unset) {
-                return i
-            }
-        }
-        return -1
-    }
-
-    /**
-     * Find the next item relative to [item] set to target span
-     * @return found item index or [upperBound] if it doesn't exist.
-     */
-    fun findNextItemIndex(item: Int, target: Int): Int {
-        for (i in item + 1 until upperBound()) {
-            val span = getSpan(i)
-            if (span == target || span == Unset) {
-                return i
-            }
-        }
-        return upperBound()
-    }
-
-    fun ensureValidIndex(requestedIndex: Int) {
-        val requestedCapacity = requestedIndex - anchor
-
-        if (requestedCapacity in 0 until MaxCapacity) {
-            // simplest path - just grow array to given capacity
-            ensureCapacity(requestedCapacity + 1)
-        } else {
-            // requested index is beyond current span bounds
-            // rebase anchor so that requested index is in the middle of span array
-            val oldAnchor = anchor
-            anchor = maxOf(requestedIndex - (spans.size / 2), 0)
-            var delta = anchor - oldAnchor
-
-            if (delta >= 0) {
-                // copy previous span data if delta is smaller than span size
-                if (delta < spans.size) {
-                    spans.copyInto(
-                        spans,
-                        destinationOffset = 0,
-                        startIndex = delta,
-                        endIndex = spans.size
-                    )
-                }
-                // fill the rest of the spans with default values
-                spans.fill(0, maxOf(0, spans.size - delta), spans.size)
-            } else {
-                delta = -delta
-                // check if we can grow spans to match delta
-                if (spans.size + delta < MaxCapacity) {
-                    // grow spans and leave space in the start
-                    ensureCapacity(spans.size + delta + 1, delta)
-                } else {
-                    // otherwise, just move data that fits
-                    if (delta < spans.size) {
-                        spans.copyInto(
-                            spans,
-                            destinationOffset = delta,
-                            startIndex = 0,
-                            endIndex = spans.size - delta
-                        )
-                    }
-                    // fill the rest of the spans with default values
-                    spans.fill(0, 0, minOf(spans.size, delta))
-                }
-            }
-        }
-    }
-
-    private fun ensureCapacity(capacity: Int, newOffset: Int = 0) {
-        require(capacity <= MaxCapacity) {
-            "Requested span capacity $capacity is larger than max supported: $MaxCapacity!"
-        }
-        if (spans.size < capacity) {
-            var newSize = spans.size
-            while (newSize < capacity) newSize *= 2
-            spans = spans.copyInto(IntArray(newSize), destinationOffset = newOffset)
-        }
-    }
-
-    companion object {
-        private const val MaxCapacity = 131_072 // Closest to 100_000, 2 ^ 17
-        internal const val Unset = -1
-    }
-}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
index cd08ee3..bf95c2e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
@@ -27,13 +27,16 @@
 import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState
 import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState.PrefetchHandle
 import androidx.compose.foundation.lazy.layout.animateScrollToItem
+import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridLaneInfo.Companion.Unset
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.saveable.listSaver
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
+import androidx.compose.runtime.structuralEqualityPolicy
 import androidx.compose.ui.layout.Remeasurement
 import androidx.compose.ui.layout.RemeasurementModifier
 import androidx.compose.ui.unit.Constraints
@@ -94,12 +97,13 @@
      * This property is observable and when use it in composable function it will be recomposed on
      * each scroll, potentially causing performance issues.
      */
-    val firstVisibleItemIndex: Int
-        get() = scrollPosition.indices.minOfOrNull {
+    val firstVisibleItemIndex: Int by derivedStateOf(structuralEqualityPolicy()) {
+        scrollPosition.indices.minOfOrNull {
             // index array can contain -1, indicating lane being empty (cell number > itemCount)
             // if any of the lanes are empty, we always on 0th item index
             if (it == -1) 0 else it
         } ?: 0
+    }
 
     /**
      * Current offset of the item with [firstVisibleItemIndex] relative to the container start.
@@ -107,10 +111,19 @@
      * This property is observable and when use it in composable function it will be recomposed on
      * each scroll, potentially causing performance issues.
      */
-    val firstVisibleItemScrollOffset: Int
-        get() = scrollPosition.offsets.run {
-            if (isEmpty()) 0 else this[scrollPosition.indices.indexOfMinValue()]
+    val firstVisibleItemScrollOffset: Int by derivedStateOf(structuralEqualityPolicy()) {
+        scrollPosition.offsets.let { offsets ->
+            val firstVisibleIndex = firstVisibleItemIndex
+            val indices = scrollPosition.indices
+            var minOffset = Int.MAX_VALUE
+            for (lane in offsets.indices) {
+                if (indices[lane] == firstVisibleIndex) {
+                    minOffset = minOf(minOffset, offsets[lane])
+                }
+            }
+            if (minOffset == Int.MAX_VALUE) 0 else minOffset
         }
+    }
 
     /** holder for current scroll position **/
     internal val scrollPosition = LazyStaggeredGridScrollPosition(
@@ -133,7 +146,7 @@
         mutableStateOf(EmptyLazyStaggeredGridLayoutInfo)
 
     /** storage for lane assignments for each item for consistent scrolling in both directions **/
-    internal val spans = LazyStaggeredGridSpans()
+    internal val laneInfo = LazyStaggeredGridLaneInfo()
 
     override var canScrollForward: Boolean by mutableStateOf(false)
         private set
@@ -170,9 +183,11 @@
     /* @VisibleForTesting */
     internal var measurePassCount = 0
 
-    /** states required for prefetching **/
+    /** transient information from measure required for prefetching **/
     internal var isVertical = false
     internal var laneWidthsPrefixSum: IntArray = IntArray(0)
+    internal var spanProvider: LazyStaggeredGridSpanProvider? = null
+    /** prefetch state **/
     private var prefetchBaseIndex: Int = -1
     private val currentItemPrefetchHandles = mutableMapOf<Int, PrefetchHandle>()
 
@@ -329,15 +344,15 @@
 
                 // find the next item for each line and prefetch if it is valid
                 targetIndex = if (scrollingForward) {
-                    spans.findNextItemIndex(previousIndex, lane)
+                    laneInfo.findNextItemIndex(previousIndex, lane)
                 } else {
-                    spans.findPreviousItemIndex(previousIndex, lane)
+                    laneInfo.findPreviousItemIndex(previousIndex, lane)
                 }
                 if (
                     targetIndex !in (0 until info.totalItemsCount) ||
-                    previousIndex == targetIndex
+                        targetIndex in prefetchHandlesUsed
                 ) {
-                    return
+                    break
                 }
 
                 prefetchHandlesUsed += targetIndex
@@ -345,8 +360,12 @@
                     continue
                 }
 
-                val crossAxisSize = laneWidthsPrefixSum[lane] -
-                    if (lane == 0) 0 else laneWidthsPrefixSum[lane - 1]
+                val isFullSpan = spanProvider?.isFullSpan(targetIndex) == true
+                val slot = if (isFullSpan) 0 else lane
+                val span = if (isFullSpan) laneCount else 1
+
+                val crossAxisSize = laneWidthsPrefixSum[slot + span - 1] -
+                    if (slot == 0) 0 else laneWidthsPrefixSum[slot - 1]
 
                 val constraints = if (isVertical) {
                     Constraints.fixedWidth(crossAxisSize)
@@ -399,19 +418,22 @@
     }
 
     private fun fillNearestIndices(itemIndex: Int, laneCount: Int): IntArray {
-        // reposition spans if needed to ensure valid indices
-        spans.ensureValidIndex(itemIndex + laneCount)
-
-        val span = spans.getSpan(itemIndex)
-        val targetLaneIndex =
-            if (span == LazyStaggeredGridSpans.Unset) 0 else minOf(span, laneCount)
         val indices = IntArray(laneCount)
+        if (spanProvider?.isFullSpan(itemIndex) == true) {
+            indices.fill(itemIndex)
+            return indices
+        }
+
+        // reposition spans if needed to ensure valid indices
+        laneInfo.ensureValidIndex(itemIndex + laneCount)
+        val previousLane = laneInfo.getLane(itemIndex)
+        val targetLaneIndex = if (previousLane == Unset) 0 else minOf(previousLane, laneCount)
 
         // fill lanes before starting index
         var currentItemIndex = itemIndex
         for (lane in (targetLaneIndex - 1) downTo 0) {
-            indices[lane] = spans.findPreviousItemIndex(currentItemIndex, lane)
-            if (indices[lane] == -1) {
+            indices[lane] = laneInfo.findPreviousItemIndex(currentItemIndex, lane)
+            if (indices[lane] == Unset) {
                 indices.fill(-1, toIndex = lane)
                 break
             }
@@ -423,7 +445,7 @@
         // fill lanes after starting index
         currentItemIndex = itemIndex
         for (lane in (targetLaneIndex + 1) until laneCount) {
-            indices[lane] = spans.findNextItemIndex(currentItemIndex, lane)
+            indices[lane] = laneInfo.findNextItemIndex(currentItemIndex, lane)
             currentItemIndex = indices[lane]
         }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester.kt
index d178c065..0636f8c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/relocation/BringIntoViewRequester.kt
@@ -29,16 +29,16 @@
 
 /**
  * Can be used to send [bringIntoView] requests. Pass it as a parameter to
- * [Modifier.bringIntoView()][bringIntoView].
+ * [Modifier.bringIntoViewRequester()][bringIntoViewRequester].
  *
- * For instance, you can call [BringIntoViewRequester.bringIntoView][bringIntoView] to
- * make all the scrollable parents scroll so that the specified item is brought into parent
- * bounds. This sample demonstrates this use case:
+ * For instance, you can call [bringIntoView()][bringIntoView] to make all the
+ * scrollable parents scroll so that the specified item is brought into the
+ * parent bounds.
  *
  * Here is a sample where a composable is brought into view:
  * @sample androidx.compose.foundation.samples.BringIntoViewSample
  *
- * Here is a sample where a part of a composable is brought into view:
+ * Here is a sample where part of a composable is brought into view:
  * @sample androidx.compose.foundation.samples.BringPartOfComposableIntoViewSample
  */
 @ExperimentalFoundationApi
@@ -82,15 +82,20 @@
 }
 
 /**
- * This is a modifier that can be used to send bringIntoView requests.
+ * Modifier that can be used to send
+ * [bringIntoView][BringIntoViewRequester.bringIntoView] requests.
  *
- * Here is an example where the a [bringIntoViewRequester] can be used to bring an item into parent
- * bounds. It demonstrates how a composable can ask its parents to scroll so that the component
- * using this modifier is brought into the bounds of all its parents.
+ * The following example uses a `bringIntoViewRequester` to bring an item into
+ * the parent bounds. The example demonstrates how a composable can ask its
+ * parents to scroll so that the component using this modifier is brought into
+ * the bounds of all its parents.
+ *
  * @sample androidx.compose.foundation.samples.BringIntoViewSample
  *
- * @param bringIntoViewRequester an instance of [BringIntoViewRequester]. This hoisted object can be
- * used to send bringIntoView requests to parents of the current composable.
+ * @param bringIntoViewRequester An instance of [BringIntoViewRequester]. This
+ *     hoisted object can be used to send
+ *     [bringIntoView][BringIntoViewRequester.bringIntoView] requests to parents
+ *     of the current composable.
  */
 @ExperimentalFoundationApi
 fun Modifier.bringIntoViewRequester(
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/ClickableText.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/ClickableText.kt
index a14f38b..05b8dad 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/ClickableText.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/ClickableText.kt
@@ -15,17 +15,23 @@
  */
 package androidx.compose.foundation.text
 
+import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.PointerEventPass
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.MultiParagraph
 import androidx.compose.ui.text.TextLayoutResult
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.text.style.TextOverflow
+import kotlinx.coroutines.launch
 
 /**
  * A continent version of [BasicText] component to be able to handle click event on the text.
@@ -92,4 +98,95 @@
             onTextLayout(it)
         }
     )
-}
\ No newline at end of file
+}
+
+/**
+ * A continent version of [BasicText] component to be able to handle click event on the text.
+ *
+ * This is a shorthand of [BasicText] with [pointerInput] to be able to handle click
+ * event easily.
+ *
+ * @sample androidx.compose.foundation.samples.ClickableText
+ *
+ * For other gestures, e.g. long press, dragging, follow sample code.
+ *
+ * @sample androidx.compose.foundation.samples.LongClickableText
+ *
+ * @see BasicText
+ * @see androidx.compose.ui.input.pointer.pointerInput
+ * @see androidx.compose.foundation.gestures.detectTapGestures
+ *
+ * @param text The text to be displayed.
+ * @param modifier Modifier to apply to this layout node.
+ * @param style Style configuration for the text such as color, font, line height etc.
+ * @param softWrap Whether the text should break at soft line breaks. If false, the glyphs in the
+ * text will be positioned as if there was unlimited horizontal space. If [softWrap] is false,
+ * [overflow] and [TextAlign] may have unexpected effects.
+ * @param overflow How visual overflow should be handled.
+ * @param maxLines An optional maximum number of lines for the text to span, wrapping if
+ * necessary. If the text exceeds the given number of lines, it will be truncated according to
+ * [overflow] and [softWrap]. If it is not null, then it must be greater than zero.
+ * @param onTextLayout Callback that is executed when a new text layout is calculated. A
+ * [TextLayoutResult] object that callback provides contains paragraph information, size of the
+ * text, baselines and other details. The callback can be used to add additional decoration or
+ * functionality to the text. For example, to draw selection around the text.
+ * @param onHover Callback that is executed when user hovers over the text with a mouse or trackpad.
+ * This callback is called with the hovered character's offset or null if the cursor is no longer
+ * hovering this.
+ * @param onClick Callback that is executed when users click the text. This callback is called
+ * with clicked character's offset.
+ */
+@ExperimentalFoundationApi // when removing this experimental annotation,
+// onHover should be nullable with null as default. The other ClickableText
+// should be deprecated as hidden and simply call this function.
+@Composable
+fun ClickableText(
+    text: AnnotatedString,
+    onHover: ((Int?) -> Unit),
+    modifier: Modifier = Modifier,
+    style: TextStyle = TextStyle.Default,
+    softWrap: Boolean = true,
+    overflow: TextOverflow = TextOverflow.Clip,
+    maxLines: Int = Int.MAX_VALUE,
+    onTextLayout: (TextLayoutResult) -> Unit = {},
+    onClick: (Int) -> Unit
+) {
+    val layoutResult = remember { mutableStateOf<TextLayoutResult?>(null) }
+    val coroutineScope = rememberCoroutineScope()
+
+    fun getOffset(positionOffset: Offset): Int? = layoutResult.value
+        ?.multiParagraph
+        ?.takeIf { it.containsWithinBounds(positionOffset) }
+        ?.getOffsetForPosition(positionOffset)
+
+    val pointerInputModifier = Modifier.pointerInput(onClick, onHover) {
+        coroutineScope.launch {
+            var previousIndex: Int? = null
+            detectMoves(PointerEventPass.Main) { pos ->
+                val index = getOffset(pos)
+                if (previousIndex != index) {
+                    previousIndex = index
+                    onHover(index)
+                }
+            }
+        }
+
+        detectTapGestures { pos -> getOffset(pos)?.let(onClick) }
+    }
+
+    BasicText(
+        text = text,
+        modifier = modifier.then(pointerInputModifier),
+        style = style,
+        softWrap = softWrap,
+        overflow = overflow,
+        maxLines = maxLines,
+        onTextLayout = {
+            layoutResult.value = it
+            onTextLayout(it)
+        }
+    )
+}
+
+private fun MultiParagraph.containsWithinBounds(positionOffset: Offset): Boolean =
+    positionOffset.let { (x, y) -> x > 0 && y >= 0 && x <= width && y <= height }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/PointerMoveDetector.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/PointerMoveDetector.kt
new file mode 100644
index 0000000..994642f
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/PointerMoveDetector.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.compose.foundation.text
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.PointerInputScope
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.currentCoroutineContext
+import kotlinx.coroutines.isActive
+
+/**
+ * Detects pointer events that result from pointer movements and feed said events to the
+ * [onMove] function. When multiple pointers are being used, only the first one is tracked.
+ * If the first pointer is then removed, the second pointer will take its place as the first
+ * pointer and be tracked.
+ *
+ * @param pointerEventPass which pass to capture the pointer event from, see [PointerEventPass]
+ * @param onMove function that handles the position of move events
+ */
+internal suspend fun PointerInputScope.detectMoves(
+    pointerEventPass: PointerEventPass = PointerEventPass.Initial,
+    onMove: (Offset) -> Unit
+) = coroutineScope {
+    val currentContext = currentCoroutineContext()
+    awaitPointerEventScope {
+        var previousPosition: Offset? = null
+        while (currentContext.isActive) {
+            val event = awaitPointerEvent(pointerEventPass)
+            when (event.type) {
+                PointerEventType.Move, PointerEventType.Enter, PointerEventType.Exit ->
+                    event.changes.first().position
+                        .takeUnless { it == previousPosition }
+                        ?.let { position ->
+                            previousPosition = position
+                            onMove(position)
+                        }
+            }
+        }
+    }
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldCursor.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldCursor.kt
index bf87a63..69c0ab3 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldCursor.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldCursor.kt
@@ -23,6 +23,7 @@
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.MotionDurationScale
 import androidx.compose.ui.composed
 import androidx.compose.ui.draw.drawWithContent
 import androidx.compose.ui.geometry.Offset
@@ -33,6 +34,7 @@
 import androidx.compose.ui.text.input.OffsetMapping
 import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.withContext
 
 @Suppress("ModifierInspectorInfo")
 internal fun Modifier.cursor(
@@ -46,10 +48,13 @@
     val isBrushSpecified = !(cursorBrush is SolidColor && cursorBrush.value.isUnspecified)
     if (state.hasFocus && value.selection.collapsed && isBrushSpecified) {
         LaunchedEffect(value.annotatedString, value.selection) {
-            // ensure that the value is always 1f _this_ frame by calling snapTo
-            cursorAlpha.snapTo(1f)
-            // then start the cursor blinking on animation clock (500ms on to start)
-            cursorAlpha.animateTo(0f, cursorAnimationSpec)
+            // Animate the cursor even when animations are disabled by the system.
+            withContext(FixedMotionDurationScale) {
+                // ensure that the value is always 1f _this_ frame by calling snapTo
+                cursorAlpha.snapTo(1f)
+                // then start the cursor blinking on animation clock (500ms on to start)
+                cursorAlpha.animateTo(0f, cursorAnimationSpec)
+            }
         }
         drawWithContent {
             this.drawContent()
@@ -88,3 +93,8 @@
 )
 
 internal val DefaultCursorThickness = 2.dp
+
+private object FixedMotionDurationScale : MotionDurationScale {
+    override val scaleFactor: Float
+        get() = 1f
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionAdjustment.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionAdjustment.kt
index 9a3a76d..3de82bb 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionAdjustment.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionAdjustment.kt
@@ -180,7 +180,7 @@
          * selection.
          *  b.if the previous start/end offset is a word boundary, use word based selection.
          *
-         *  Notice that this selection adjustment assumes that when isStartHandle is ture, only
+         *  Notice that this selection adjustment assumes that when isStartHandle is true, only
          *  start handle is moving(or unchanged), and vice versa.
          */
         val CharacterWithWordAccelerate = object : SelectionAdjustment {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionMode.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionMode.kt
index 5c1ef9e..23c749d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionMode.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionMode.kt
@@ -99,7 +99,7 @@
             return true
         }
         // Compare the location of start and end to the bound. If both are on the same side, return
-        // false, otherwise return ture.
+        // false, otherwise return true.
         val compareStart = compare(start, bounds)
         val compareEnd = compare(end, bounds)
         return (compareStart > 0) xor (compareEnd > 0)
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Clickable.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Clickable.desktop.kt
index 48eb943..6d663a8 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Clickable.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Clickable.desktop.kt
@@ -19,6 +19,7 @@
 import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.interaction.PressInteraction
+import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -51,6 +52,9 @@
 import androidx.compose.ui.util.fastAll
 import java.awt.event.KeyEvent.VK_ENTER
 
+@Composable
+internal actual fun isComposeRootInScrollableContainer(): () -> Boolean = { false }
+
 // TODO: b/168524931 - should this depend on the input device?
 internal actual val TapIndicationDelay: Long = 0L
 
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/PointerMoveDetectorTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/PointerMoveDetectorTest.kt
new file mode 100644
index 0000000..aa03db7
--- /dev/null
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/PointerMoveDetectorTest.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text
+
+import androidx.compose.foundation.gestures.SuspendingGestureTestUtil
+import androidx.compose.ui.geometry.Offset
+import com.google.common.truth.Correspondence
+import com.google.common.truth.IterableSubject
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class PointerMoveDetectorTest {
+    @Test
+    fun whenSimpleMovement_allMovesAreReported() {
+        val actualMoves = mutableListOf<Offset>()
+        SuspendingGestureTestUtil {
+            detectMoves { actualMoves.add(it) }
+        }.executeInComposition {
+            down(5f, 5f)
+                .moveTo(4f, 4f)
+                .moveTo(3f, 3f)
+                .moveTo(2f, 2f)
+                .moveTo(1f, 1f)
+                .up()
+
+            assertThat(actualMoves).hasEqualOffsets(
+                listOf(
+                    Offset(4f, 4f),
+                    Offset(3f, 3f),
+                    Offset(2f, 2f),
+                    Offset(1f, 1f),
+                )
+            )
+        }
+    }
+
+    @Test
+    fun whenMultiplePointers_onlyUseFirst() {
+        val actualMoves = mutableListOf<Offset>()
+        SuspendingGestureTestUtil {
+            detectMoves { actualMoves.add(it) }
+        }.executeInComposition {
+            var m1 = down(5f, 5f)
+            var m2 = down(6f, 6f)
+            m1 = m1.moveTo(4f, 4f)
+            m2 = m2.moveTo(7f, 7f)
+            m1 = m1.moveTo(3f, 3f)
+            m2 = m2.moveTo(8f, 8f)
+            m1 = m1.moveTo(2f, 2f)
+            m2 = m2.moveTo(9f, 9f)
+            m1.moveTo(1f, 1f)
+            m2.moveTo(10f, 10f)
+            m1.up()
+            m2.up()
+
+            assertThat(actualMoves).hasEqualOffsets(
+                listOf(
+                    Offset(4f, 4f),
+                    Offset(3f, 3f),
+                    Offset(2f, 2f),
+                    Offset(1f, 1f),
+                )
+            )
+        }
+    }
+
+    @Test
+    fun whenMultiplePointers_thenFirstReleases_handOffToNextPointer() {
+        val actualMoves = mutableListOf<Offset>()
+        SuspendingGestureTestUtil {
+            detectMoves { actualMoves.add(it) }
+        }.executeInComposition {
+            var m1 = down(5f, 5f) // ignored because not a move
+            m1 = m1.moveTo(4f, 4f) // used
+            m1 = m1.moveTo(3f, 3f) // used
+            var m2 = down(4f, 4f) // ignored because still tracking m1
+            m1 = m1.moveTo(2f, 2f) // used
+            m2 = m2.moveTo(3f, 3f) // ignored because still tracking m1
+            m1.up() // ignored because not a move
+            m2.moveTo(2f, 2f) // ignored because equal to the previous used move
+            m2.moveTo(1f, 1f) // used
+            m2.up() // ignored because not a move
+
+            assertThat(actualMoves).hasEqualOffsets(
+                listOf(
+                    Offset(4f, 4f),
+                    Offset(3f, 3f),
+                    Offset(2f, 2f),
+                    Offset(1f, 1f),
+                )
+            )
+        }
+    }
+
+    private fun IterableSubject.hasEqualOffsets(expectedMoves: List<Offset>) {
+        comparingElementsUsing(offsetCorrespondence)
+            .containsExactly(*expectedMoves.toTypedArray())
+            .inOrder()
+    }
+
+    private val offsetCorrespondence: Correspondence<Offset, Offset> = Correspondence.from(
+        { o1, o2 -> o1!!.x == o2!!.x && o1.y == o2.y },
+        "has the offset of",
+    )
+}
diff --git a/compose/integration-tests/demos/src/androidTest/java/androidx/compose/integration/demos/test/DemoTest.kt b/compose/integration-tests/demos/src/androidTest/java/androidx/compose/integration/demos/test/DemoTest.kt
index 68967e3..a99722f 100644
--- a/compose/integration-tests/demos/src/androidTest/java/androidx/compose/integration/demos/test/DemoTest.kt
+++ b/compose/integration-tests/demos/src/androidTest/java/androidx/compose/integration/demos/test/DemoTest.kt
@@ -110,6 +110,7 @@
         onNodeWithTag(Tags.AppBarTitle).assertTextEquals(demo.title)
     }
 
+    @Ignore("b/265281736")
     @Test
     fun navigateThroughAllDemos_1() {
         navigateThroughAllDemos(SplitDemoCategories[0])
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/DrawerTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/DrawerTest.kt
index 59a1fb9..391ce209 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/DrawerTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/DrawerTest.kt
@@ -26,8 +26,6 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.canScroll
-import androidx.compose.ui.input.consumeScrollContainerInfo
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsActions
@@ -1205,76 +1203,4 @@
         topNode = rule.onNodeWithTag(topTag).fetchSemanticsNode()
         assertEquals(2, topNode.children.size)
     }
-
-    @Test
-    fun modalDrawer_providesScrollableContainerInfo_enabled() {
-        var actualValue = { false }
-        rule.setMaterialContent {
-            ModalDrawer(
-                drawerContent = {},
-                content = {
-                    Box(Modifier.consumeScrollContainerInfo {
-                        actualValue = { it!!.canScroll() }
-                    })
-                }
-            )
-        }
-
-        assertThat(actualValue()).isTrue()
-    }
-
-    @Test
-    fun modalDrawer_providesScrollableContainerInfo_disabled() {
-        var actualValue = { false }
-        rule.setMaterialContent {
-            ModalDrawer(
-                drawerContent = {},
-                gesturesEnabled = false,
-                content = {
-                    Box(Modifier.consumeScrollContainerInfo {
-                        actualValue = { it!!.canScroll() }
-                    })
-                }
-            )
-        }
-
-        assertThat(actualValue()).isFalse()
-    }
-
-    @OptIn(ExperimentalMaterialApi::class)
-    @Test
-    fun bottomDrawer_providesScrollableContainerInfo_enabled() {
-        var actualValue = { false }
-        rule.setMaterialContent {
-            BottomDrawer(
-                drawerContent = {},
-                content = {
-                    Box(Modifier.consumeScrollContainerInfo {
-                        actualValue = { it!!.canScroll() }
-                    })
-                }
-            )
-        }
-
-        assertThat(actualValue()).isTrue()
-    }
-
-    @OptIn(ExperimentalMaterialApi::class)
-    @Test
-    fun bottomDrawer_providesScrollableContainerInfo_disabled() {
-        var actualValue = { false }
-        rule.setMaterialContent {
-            BottomDrawer(
-                drawerContent = {},
-                gesturesEnabled = false,
-                content = {
-                    Box(Modifier.consumeScrollContainerInfo {
-                        actualValue = { it!!.canScroll() }
-                    })
-                }
-            )
-        }
-
-        assertThat(actualValue()).isFalse()
-    }
 }
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt
index 128ad57..44bff47 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt
@@ -36,8 +36,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.canScroll
-import androidx.compose.ui.input.consumeScrollContainerInfo
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.layout.positionInRoot
 import androidx.compose.ui.platform.testTag
@@ -834,56 +832,6 @@
     }
 
     @Test
-    fun modalBottomSheet_providesScrollableContainerInfo_hidden() {
-        var actualValue = { false }
-        rule.setMaterialContent {
-            ModalBottomSheetLayout(
-                sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden),
-                content = { Box(
-                    Modifier
-                        .fillMaxSize()
-                        .testTag(contentTag)) },
-                sheetContent = {
-                    Box(
-                        Modifier
-                            .fillMaxSize()
-                            .consumeScrollContainerInfo {
-                                actualValue = { it!!.canScroll() }
-                            }
-                    )
-                }
-            )
-        }
-
-        assertThat(actualValue()).isFalse()
-    }
-
-    @Test
-    fun modalBottomSheet_providesScrollableContainerInfo_expanded() {
-        var actualValue = { false }
-        rule.setMaterialContent {
-            ModalBottomSheetLayout(
-                sheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Expanded),
-                content = { Box(
-                    Modifier
-                        .fillMaxSize()
-                        .testTag(contentTag)) },
-                sheetContent = {
-                    Box(
-                        Modifier
-                            .fillMaxSize()
-                            .consumeScrollContainerInfo {
-                                actualValue = { it!!.canScroll() }
-                            }
-                    )
-                }
-            )
-        }
-
-        assertThat(actualValue()).isTrue()
-    }
-
-    @Test
     fun modalBottomSheet_nestedScroll_consumesWithinBounds_scrollsOutsideBounds() {
         lateinit var sheetState: ModalBottomSheetState
         lateinit var scrollState: ScrollState
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
index 91f2417..8780e39 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Drawer.kt
@@ -43,10 +43,8 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.isSpecified
-import androidx.compose.ui.input.ScrollContainerInfo
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.input.provideScrollContainerInfo
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
@@ -398,14 +396,6 @@
     content: @Composable () -> Unit
 ) {
     val scope = rememberCoroutineScope()
-
-    val containerInfo = remember(gesturesEnabled) {
-        object : ScrollContainerInfo {
-            override fun canScrollHorizontally() = gesturesEnabled
-
-            override fun canScrollVertically() = false
-        }
-    }
     BoxWithConstraints(modifier.fillMaxSize()) {
         val modalDrawerConstraints = constraints
         // TODO : think about Infinite max bounds case
@@ -434,7 +424,6 @@
                         DrawerValue.Open -> maxValue
                     }
                 }
-                .provideScrollContainerInfo(containerInfo)
         ) {
             Box {
                 content()
@@ -575,15 +564,6 @@
         } else {
             Modifier
         }
-
-        val containerInfo = remember(gesturesEnabled) {
-            object : ScrollContainerInfo {
-                override fun canScrollHorizontally() = gesturesEnabled
-
-                override fun canScrollVertically() = false
-            }
-        }
-
         val swipeable = Modifier
             .then(nestedScroll)
             .swipeable(
@@ -593,7 +573,6 @@
                 enabled = gesturesEnabled,
                 resistance = null
             )
-            .provideScrollContainerInfo(containerInfo)
 
         Box(swipeable) {
             content()
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
index 6e85420..d15e372 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
@@ -47,12 +47,10 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.isSpecified
-import androidx.compose.ui.input.ScrollContainerInfo
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.input.provideScrollContainerInfo
 import androidx.compose.ui.semantics.collapse
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.dismiss
@@ -467,15 +465,6 @@
                 visible = sheetState.swipeableState.targetValue != Hidden
             )
         }
-
-        val containerInfo = remember(sheetState) {
-            object : ScrollContainerInfo {
-                override fun canScrollHorizontally() = false
-
-                override fun canScrollVertically() = sheetState.currentValue != Hidden
-            }
-        }
-
         Surface(
             Modifier
                 .align(Alignment.TopCenter) // We offset from the top so we'll center from there
@@ -520,7 +509,6 @@
                         } else null
                     }
                 }
-                .provideScrollContainerInfo(containerInfo)
                 .semantics {
                     if (sheetState.isVisible) {
                         dismiss {
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index f0a3e12..e3bd3b6 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -232,6 +232,9 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> LocalContentColor;
   }
 
+  public final class DatePickerDialog_androidKt {
+  }
+
   public final class DatePickerKt {
   }
 
@@ -597,7 +600,7 @@
   @androidx.compose.runtime.Immutable public final class SliderColors {
   }
 
-  public final class SliderDefaults {
+  @androidx.compose.runtime.Stable public final class SliderDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.material3.SliderColors colors(optional long thumbColor, optional long activeTrackColor, optional long activeTickColor, optional long inactiveTrackColor, optional long inactiveTickColor, optional long disabledThumbColor, optional long disabledActiveTrackColor, optional long disabledActiveTickColor, optional long disabledInactiveTrackColor, optional long disabledInactiveTickColor);
     field public static final androidx.compose.material3.SliderDefaults INSTANCE;
   }
diff --git a/compose/material3/material3/api/public_plus_experimental_current.txt b/compose/material3/material3/api/public_plus_experimental_current.txt
index cccdf28..d102ce1 100644
--- a/compose/material3/material3/api/public_plus_experimental_current.txt
+++ b/compose/material3/material3/api/public_plus_experimental_current.txt
@@ -288,11 +288,19 @@
     method @androidx.compose.runtime.Composable public void DatePickerHeadline(androidx.compose.material3.DatePickerState state, androidx.compose.material3.DatePickerFormatter dateFormatter);
     method @androidx.compose.runtime.Composable public void DatePickerTitle();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.DatePickerColors colors(optional long containerColor, optional long titleContentColor, optional long headlineContentColor, optional long weekdayContentColor, optional long subheadContentColor, optional long yearContentColor, optional long currentYearContentColor, optional long selectedYearContentColor, optional long selectedYearContainerColor, optional long dayContentColor, optional long disabledDayContentColor, optional long selectedDayContentColor, optional long disabledSelectedDayContentColor, optional long selectedDayContainerColor, optional long disabledSelectedDayContainerColor, optional long todayContentColor, optional long todayDateBorderColor);
+    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
+    method public float getTonalElevation();
     method public kotlin.ranges.IntRange getYearRange();
+    property public final float TonalElevation;
     property public final kotlin.ranges.IntRange YearRange;
+    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape shape;
     field public static final androidx.compose.material3.DatePickerDefaults INSTANCE;
   }
 
+  public final class DatePickerDialog_androidKt {
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void DatePickerDialog(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, kotlin.jvm.functions.Function0<kotlin.Unit> confirmButton, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? dismissButton, optional androidx.compose.ui.graphics.Shape shape, optional float tonalElevation, optional androidx.compose.material3.DatePickerColors colors, optional androidx.compose.ui.window.DialogProperties properties, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+  }
+
   @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class DatePickerFormatter {
     ctor public DatePickerFormatter(optional String shortFormat, optional String mediumFormat, optional String monthYearFormat);
   }
@@ -824,7 +832,7 @@
   @androidx.compose.runtime.Immutable public final class SliderColors {
   }
 
-  public final class SliderDefaults {
+  @androidx.compose.runtime.Stable public final class SliderDefaults {
     method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void Thumb(androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled, optional long thumbSize);
     method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void Track(androidx.compose.material3.SliderPositions sliderPositions, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SliderColors colors, optional boolean enabled);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.SliderColors colors(optional long thumbColor, optional long activeTrackColor, optional long activeTickColor, optional long inactiveTrackColor, optional long inactiveTickColor, optional long disabledThumbColor, optional long disabledActiveTrackColor, optional long disabledActiveTickColor, optional long disabledInactiveTrackColor, optional long disabledInactiveTickColor);
@@ -832,17 +840,17 @@
   }
 
   public final class SliderKt {
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RangeSlider(kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> value, kotlin.jvm.functions.Function1<? super kotlin.ranges.ClosedFloatingPointRange<java.lang.Float>,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RangeSlider(kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> value, kotlin.jvm.functions.Function1<? super kotlin.ranges.ClosedFloatingPointRange<java.lang.Float>,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource startInteractionSource, optional androidx.compose.foundation.interaction.MutableInteractionSource endInteractionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderPositions,kotlin.Unit> startThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderPositions,kotlin.Unit> endThumb, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderPositions,kotlin.Unit> track, optional int steps);
     method @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
     method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderPositions,kotlin.Unit> thumb);
     method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void Slider(float value, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderPositions,kotlin.Unit> track, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, optional int steps, optional kotlin.jvm.functions.Function0<kotlin.Unit>? onValueChangeFinished, optional androidx.compose.material3.SliderColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SliderPositions,kotlin.Unit> thumb);
   }
 
   @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SliderPositions {
-    ctor public SliderPositions(float initialPositionFraction, float[] initialTickFractions);
-    method public float getPositionFraction();
+    ctor public SliderPositions(optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> initialActiveRange, optional float[] initialTickFractions);
+    method public kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> getActiveRange();
     method public float[] getTickFractions();
-    property public final float positionFraction;
+    property public final kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> activeRange;
     property public final float[] tickFractions;
   }
 
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index f0a3e12..e3bd3b6 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -232,6 +232,9 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> LocalContentColor;
   }
 
+  public final class DatePickerDialog_androidKt {
+  }
+
   public final class DatePickerKt {
   }
 
@@ -597,7 +600,7 @@
   @androidx.compose.runtime.Immutable public final class SliderColors {
   }
 
-  public final class SliderDefaults {
+  @androidx.compose.runtime.Stable public final class SliderDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.material3.SliderColors colors(optional long thumbColor, optional long activeTrackColor, optional long activeTickColor, optional long inactiveTrackColor, optional long inactiveTickColor, optional long disabledThumbColor, optional long disabledActiveTrackColor, optional long disabledActiveTickColor, optional long disabledInactiveTrackColor, optional long disabledInactiveTickColor);
     field public static final androidx.compose.material3.SliderDefaults INSTANCE;
   }
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
index e7d2e26..33ddf18 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
@@ -38,6 +38,7 @@
 import androidx.compose.material3.samples.ClickableCardSample
 import androidx.compose.material3.samples.ClickableElevatedCardSample
 import androidx.compose.material3.samples.ClickableOutlinedCardSample
+import androidx.compose.material3.samples.DatePickerDialogSample
 import androidx.compose.material3.samples.DatePickerSample
 import androidx.compose.material3.samples.DatePickerWithDateValidatorSample
 import androidx.compose.material3.samples.DismissibleNavigationDrawerSample
@@ -97,6 +98,7 @@
 import androidx.compose.material3.samples.RadioButtonSample
 import androidx.compose.material3.samples.RadioGroupSample
 import androidx.compose.material3.samples.RangeSliderSample
+import androidx.compose.material3.samples.RangeSliderWithCustomComponents
 import androidx.compose.material3.samples.ScaffoldWithCoroutinesSnackbar
 import androidx.compose.material3.samples.ScaffoldWithCustomSnackbar
 import androidx.compose.material3.samples.ScaffoldWithIndefiniteSnackbar
@@ -348,6 +350,13 @@
         DatePickerSample()
     },
     Example(
+        name = ::DatePickerDialogSample.name,
+        description = DatePickerExampleDescription,
+        sourceUrl = DatePickerExampleSourceUrl
+    ) {
+        DatePickerDialogSample()
+    },
+    Example(
         name = ::DatePickerWithDateValidatorSample.name,
         description = DatePickerExampleDescription,
         sourceUrl = DatePickerExampleSourceUrl
@@ -722,6 +731,20 @@
         StepsSliderSample()
     },
     Example(
+        name = ::SliderWithCustomThumbSample.name,
+        description = SlidersExampleDescription,
+        sourceUrl = SlidersExampleSourceUrl
+    ) {
+        SliderWithCustomThumbSample()
+    },
+    Example(
+        name = ::SliderWithCustomTrackAndThumb.name,
+        description = SlidersExampleDescription,
+        sourceUrl = SlidersExampleSourceUrl
+    ) {
+        SliderWithCustomTrackAndThumb()
+    },
+    Example(
         name = ::RangeSliderSample.name,
         description = SlidersExampleDescription,
         sourceUrl = SlidersExampleSourceUrl
@@ -736,18 +759,11 @@
         StepRangeSliderSample()
     },
     Example(
-        name = ::SliderWithCustomThumbSample.name,
+        name = ::RangeSliderWithCustomComponents.name,
         description = SlidersExampleDescription,
         sourceUrl = SlidersExampleSourceUrl
     ) {
-        SliderWithCustomThumbSample()
-    },
-    Example(
-        name = ::SliderWithCustomTrackAndThumb.name,
-        description = SlidersExampleDescription,
-        sourceUrl = SlidersExampleSourceUrl
-    ) {
-        SliderWithCustomTrackAndThumb()
+        RangeSliderWithCustomComponents()
     }
 )
 
diff --git a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/SwipeToDismissDemo.kt b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/SwipeToDismissDemo.kt
index b3796d1..69eff5a 100644
--- a/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/SwipeToDismissDemo.kt
+++ b/compose/material3/material3/integration-tests/material3-demos/src/main/java/androidx/compose/material3/demos/SwipeToDismissDemo.kt
@@ -42,13 +42,18 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.scale
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.semantics.CustomAccessibilityAction
+import androidx.compose.ui.semantics.customActions
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.launch
 
 private val items = listOf(
     "Cupcake",
@@ -79,6 +84,8 @@
     LazyColumn {
         items(items) { item ->
             var unread by remember { mutableStateOf(false) }
+            val scope = rememberCoroutineScope()
+
             val dismissState = rememberDismissState(
                 confirmValueChange = {
                     if (it == DismissValue.DismissedToEnd) unread = !unread
@@ -135,6 +142,19 @@
                                 headlineText = {
                                     Text(item, fontWeight = if (unread) FontWeight.Bold else null)
                                 },
+                                modifier = Modifier.semantics {
+                                    // Provide accessible alternatives to swipe actions.
+                                    val label = if (unread) "Mark Read" else "Mark Unread"
+                                    customActions = listOf(
+                                        CustomAccessibilityAction(label) { unread = !unread; true },
+                                        CustomAccessibilityAction("Delete") {
+                                            scope.launch {
+                                                dismissState.dismiss(DismissDirection.EndToStart)
+                                            }
+                                            true
+                                        }
+                                    )
+                                },
                                 supportingText = { Text("Swipe me left or right!") },
                             )
                         }
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/DatePickerSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/DatePickerSamples.kt
index 70f82df..e067984 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/DatePickerSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/DatePickerSamples.kt
@@ -21,10 +21,18 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.DatePicker
+import androidx.compose.material3.DatePickerDialog
 import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.SnackbarHost
+import androidx.compose.material3.SnackbarHostState
 import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
 import androidx.compose.material3.rememberDatePickerState
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
@@ -33,6 +41,7 @@
 import java.time.ZoneId
 import java.util.Calendar
 import java.util.TimeZone
+import kotlinx.coroutines.launch
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Preview
@@ -47,6 +56,57 @@
     }
 }
 
+@OptIn(ExperimentalMaterial3Api::class)
+@Preview
+@Sampled
+@Composable
+fun DatePickerDialogSample() {
+    // Decoupled snackbar host state from scaffold state for demo purposes.
+    val snackState = remember { SnackbarHostState() }
+    val snackScope = rememberCoroutineScope()
+    SnackbarHost(hostState = snackState, Modifier)
+    val openDialog = remember { mutableStateOf(true) }
+    // TODO demo how to read the selected date from the state.
+    if (openDialog.value) {
+        val datePickerState = rememberDatePickerState()
+        val confirmEnabled = derivedStateOf { datePickerState.selectedDateMillis != null }
+        DatePickerDialog(
+            onDismissRequest = {
+                // Dismiss the dialog when the user clicks outside the dialog or on the back
+                // button. If you want to disable that functionality, simply use an empty
+                // onDismissRequest.
+                openDialog.value = false
+            },
+            confirmButton = {
+                TextButton(
+                    onClick = {
+                        openDialog.value = false
+                        snackScope.launch {
+                            snackState.showSnackbar(
+                                "Selected date timestamp: ${datePickerState.selectedDateMillis}"
+                            )
+                        }
+                    },
+                    enabled = confirmEnabled.value
+                ) {
+                    Text("OK")
+                }
+            },
+            dismissButton = {
+                TextButton(
+                    onClick = {
+                        openDialog.value = false
+                    }
+                ) {
+                    Text("Cancel")
+                }
+            }
+        ) {
+            DatePicker(datePickerState = datePickerState)
+        }
+    }
+}
+
 @Suppress("ClassVerificationFailure")
 @OptIn(ExperimentalMaterial3Api::class)
 @Preview
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt
index f1165b4..154bdd2 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSamples.kt
@@ -54,7 +54,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
 @Preview
 @Sampled
 @Composable
@@ -80,49 +79,6 @@
 @Preview
 @Sampled
 @Composable
-fun RangeSliderSample() {
-    var sliderPosition by remember { mutableStateOf(0f..100f) }
-    Column {
-        Text(text = sliderPosition.toString())
-        RangeSlider(
-            modifier = Modifier.semantics { contentDescription = "Localized Description" },
-            value = sliderPosition,
-            onValueChange = { sliderPosition = it },
-            valueRange = 0f..100f,
-            onValueChangeFinished = {
-                // launch some business logic update with the state you hold
-                // viewModel.updateSelectedSliderValue(sliderPosition)
-            },
-        )
-    }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Preview
-@Sampled
-@Composable
-fun StepRangeSliderSample() {
-    var sliderPosition by remember { mutableStateOf(0f..100f) }
-    Column {
-        Text(text = sliderPosition.toString())
-        RangeSlider(
-            modifier = Modifier.semantics { contentDescription = "Localized Description" },
-            steps = 5,
-            value = sliderPosition,
-            onValueChange = { sliderPosition = it },
-            valueRange = 0f..100f,
-            onValueChangeFinished = {
-                // launch some business logic update with the state you hold
-                // viewModel.updateSelectedSliderValue(sliderPosition)
-            },
-        )
-    }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Preview
-@Sampled
-@Composable
 fun SliderWithCustomThumbSample() {
     var sliderPosition by remember { mutableStateOf(0f) }
     Column {
@@ -184,3 +140,94 @@
         )
     }
 }
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Preview
+@Sampled
+@Composable
+fun RangeSliderSample() {
+    var sliderPosition by remember { mutableStateOf(0f..100f) }
+    Column {
+        Text(text = sliderPosition.toString())
+        RangeSlider(
+            modifier = Modifier.semantics { contentDescription = "Localized Description" },
+            value = sliderPosition,
+            onValueChange = { sliderPosition = it },
+            valueRange = 0f..100f,
+            onValueChangeFinished = {
+                // launch some business logic update with the state you hold
+                // viewModel.updateSelectedSliderValue(sliderPosition)
+            },
+        )
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Preview
+@Sampled
+@Composable
+fun StepRangeSliderSample() {
+    var sliderPosition by remember { mutableStateOf(0f..100f) }
+    Column {
+        Text(text = sliderPosition.toString())
+        RangeSlider(
+            modifier = Modifier.semantics { contentDescription = "Localized Description" },
+            steps = 5,
+            value = sliderPosition,
+            onValueChange = { sliderPosition = it },
+            valueRange = 0f..100f,
+            onValueChangeFinished = {
+                // launch some business logic update with the state you hold
+                // viewModel.updateSelectedSliderValue(sliderPosition)
+            },
+        )
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Preview
+@Sampled
+@Composable
+fun RangeSliderWithCustomComponents() {
+    var sliderPosition by remember { mutableStateOf(0f..100f) }
+    val startInteractionSource = remember { MutableInteractionSource() }
+    val endInteractionSource = remember { MutableInteractionSource() }
+    val startThumbAndTrackColors = SliderDefaults.colors(
+        thumbColor = Color.Blue,
+        activeTrackColor = Color.Red
+    )
+    val endThumbColors = SliderDefaults.colors(thumbColor = Color.Green)
+    Column {
+        Text(text = sliderPosition.toString())
+        RangeSlider(
+            modifier = Modifier.semantics { contentDescription = "Localized Description" },
+            value = sliderPosition,
+            onValueChange = { sliderPosition = it },
+            valueRange = 0f..100f,
+            onValueChangeFinished = {
+                // launch some business logic update with the state you hold
+                // viewModel.updateSelectedSliderValue(sliderPosition)
+            },
+            startInteractionSource = startInteractionSource,
+            endInteractionSource = endInteractionSource,
+            startThumb = {
+                SliderDefaults.Thumb(
+                    interactionSource = startInteractionSource,
+                    colors = startThumbAndTrackColors
+                )
+            },
+            endThumb = {
+                SliderDefaults.Thumb(
+                    interactionSource = endInteractionSource,
+                    colors = endThumbColors
+                )
+            },
+            track = { sliderPositions ->
+                SliderDefaults.Track(
+                    colors = startThumbAndTrackColors,
+                    sliderPositions = sliderPositions
+                )
+            }
+        )
+    }
+}
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DatePickerScreenshotTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DatePickerScreenshotTest.kt
index 5e28a15..7a55b57 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DatePickerScreenshotTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DatePickerScreenshotTest.kt
@@ -24,6 +24,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.isDialog
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
@@ -119,6 +120,32 @@
         assertAgainstGolden("datePicker_yearPicker_${scheme.name}")
     }
 
+    @Test
+    fun datePicker_inDialog() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            val monthInUtcMillis = dayInUtcMilliseconds(year = 2021, month = 3, dayOfMonth = 1)
+            val selectedDayMillis = dayInUtcMilliseconds(year = 2021, month = 3, dayOfMonth = 6)
+            DatePickerDialog(
+                onDismissRequest = { },
+                confirmButton = { TextButton(onClick = {}) { Text("OK") } },
+                dismissButton = { TextButton(onClick = {}) { Text("Cancel") } }
+            ) {
+                DatePicker(
+                    datePickerState = rememberDatePickerState(
+                        initialDisplayedMonthMillis = monthInUtcMillis,
+                        initialSelectedDateMillis = selectedDayMillis
+                    )
+                )
+            }
+        }
+        rule.onNode(isDialog())
+            .captureToImage()
+            .assertAgainstGolden(
+                rule = screenshotRule,
+                goldenIdentifier = "datePicker_inDialog_${scheme.name}"
+            )
+    }
+
     // Returns the given date's day as milliseconds from epoch. The returned value is for the day's
     // start on midnight.
     private fun dayInUtcMilliseconds(year: Int, month: Int, dayOfMonth: Int): Long =
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DismissibleNavigationDrawerTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DismissibleNavigationDrawerTest.kt
index 6deaf4d..2edb2e5 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DismissibleNavigationDrawerTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DismissibleNavigationDrawerTest.kt
@@ -26,8 +26,6 @@
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.canScroll
-import androidx.compose.ui.input.consumeScrollContainerInfo
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsActions
@@ -560,44 +558,6 @@
                 .onParent()
                 .assert(SemanticsMatcher.keyNotDefined(SemanticsActions.Dismiss))
         }
-
-    @Test
-    fun dismissibleNavigationDrawer_providesScrollableContainerInfo_enabled() {
-        var actualValue = { false }
-        rule.setMaterialContent(lightColorScheme()) {
-
-            DismissibleNavigationDrawer(
-                gesturesEnabled = true,
-                drawerContent = {},
-                content = {
-                    Box(Modifier.consumeScrollContainerInfo {
-                        actualValue = { it!!.canScroll() }
-                    })
-                }
-            )
-        }
-
-        assertThat(actualValue()).isTrue()
-    }
-
-    @Test
-    fun dismissibleNavigationDrawer_providesScrollableContainerInfo_disabled() {
-        var actualValue = { false }
-        rule.setMaterialContent(lightColorScheme()) {
-
-            DismissibleNavigationDrawer(
-                gesturesEnabled = false,
-                drawerContent = {},
-                content = {
-                    Box(Modifier.consumeScrollContainerInfo {
-                        actualValue = { it!!.canScroll() }
-                    })
-                }
-            )
-        }
-
-        assertThat(actualValue()).isFalse()
-    }
 }
 
 private val DrawerTestTag = "drawer"
\ No newline at end of file
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalNavigationDrawerTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalNavigationDrawerTest.kt
index f18376cc..409120f 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalNavigationDrawerTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalNavigationDrawerTest.kt
@@ -26,8 +26,6 @@
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.canScroll
-import androidx.compose.ui.input.consumeScrollContainerInfo
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsActions
@@ -658,45 +656,6 @@
         topNode = rule.onNodeWithTag(topTag).fetchSemanticsNode()
         assertEquals(2, topNode.children.size)
     }
-
-    @Test
-    fun navigationDrawer_providesScrollableContainerInfo_enabled() {
-        var actualValue = { false }
-        rule.setMaterialContent(lightColorScheme()) {
-            ModalNavigationDrawer(
-                drawerContent = { ModalDrawerSheet { } },
-                content = {
-                    Box(
-                        Modifier.consumeScrollContainerInfo {
-                            actualValue = { it!!.canScroll() }
-                        }
-                    )
-                }
-            )
-        }
-
-        assertThat(actualValue()).isTrue()
-    }
-
-    @Test
-    fun navigationDrawer_providesScrollableContainerInfo_disabled() {
-        var actualValue = { false }
-        rule.setMaterialContent(lightColorScheme()) {
-            ModalNavigationDrawer(
-                gesturesEnabled = false,
-                drawerContent = { ModalDrawerSheet { } },
-                content = {
-                    Box(
-                        Modifier.consumeScrollContainerInfo {
-                            actualValue = { it!!.canScroll() }
-                        }
-                    )
-                }
-            )
-        }
-
-        assertThat(actualValue()).isFalse()
-    }
 }
 
 private val DrawerTestTag = "drawer"
\ No newline at end of file
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderTest.kt
index e2f2a1c..24c2ae7 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderTest.kt
@@ -1176,6 +1176,76 @@
         rule.onAllNodes(isFocusable(), true)[1]
             .assertRangeInfoEquals(ProgressBarRangeInfo(15f, 10f..20f, 1))
     }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
+    fun rangeSlider_thumb_recomposition() {
+        val state = mutableStateOf(0f..100f)
+        val startRecompositionCounter = SliderRecompositionCounter()
+        val endRecompositionCounter = SliderRecompositionCounter()
+
+        rule.setContent {
+            RangeSlider(
+                modifier = Modifier.testTag(tag),
+                value = state.value,
+                onValueChange = { state.value = it },
+                valueRange = 0f..100f,
+                startThumb = { sliderPositions ->
+                    startRecompositionCounter.OuterContent(sliderPositions)
+                },
+                endThumb = { sliderPositions ->
+                    endRecompositionCounter.OuterContent(sliderPositions)
+                }
+            )
+        }
+
+        rule.onNodeWithTag(tag)
+            .performTouchInput {
+                down(center)
+                moveBy(Offset(100f, 0f))
+                moveBy(Offset(-100f, 0f))
+                moveBy(Offset(100f, 0f))
+            }
+
+        rule.runOnIdle {
+            Truth.assertThat(startRecompositionCounter.outerRecomposition).isEqualTo(1)
+            Truth.assertThat(startRecompositionCounter.innerRecomposition).isEqualTo(3)
+            Truth.assertThat(endRecompositionCounter.outerRecomposition).isEqualTo(1)
+            Truth.assertThat(endRecompositionCounter.innerRecomposition).isEqualTo(3)
+        }
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
+    fun rangeSlider_track_recomposition() {
+        val state = mutableStateOf(0f..100f)
+        val recompositionCounter = SliderRecompositionCounter()
+
+        rule.setContent {
+            RangeSlider(
+                modifier = Modifier.testTag(tag),
+                value = state.value,
+                onValueChange = { state.value = it },
+                valueRange = 0f..100f,
+                track = { sliderPositions ->
+                    recompositionCounter.OuterContent(sliderPositions)
+                }
+            )
+        }
+
+        rule.onNodeWithTag(tag)
+            .performTouchInput {
+                down(center)
+                moveBy(Offset(100f, 0f))
+                moveBy(Offset(-100f, 0f))
+                moveBy(Offset(100f, 0f))
+            }
+
+        rule.runOnIdle {
+            Truth.assertThat(recompositionCounter.outerRecomposition).isEqualTo(1)
+            Truth.assertThat(recompositionCounter.innerRecomposition).isEqualTo(4)
+        }
+    }
 }
 
 @Stable
@@ -1197,6 +1267,6 @@
     @Composable
     private fun InnerContent(sliderPositions: SliderPositions) {
         SideEffect { ++innerRecomposition }
-        Text("InnerContent: ${sliderPositions.positionFraction}")
+        Text("InnerContent: ${sliderPositions.activeRange}")
     }
 }
\ No newline at end of file
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SnackbarTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SnackbarTest.kt
index deb36d8..e0f7a77 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SnackbarTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SnackbarTest.kt
@@ -105,7 +105,7 @@
             }
         lateinit var dismissContentDescription: String
         rule.setMaterialContent(lightColorScheme()) {
-            dismissContentDescription = getString(string = Strings.Dismiss)
+            dismissContentDescription = getString(string = Strings.SnackbarDismiss)
             Box { Snackbar(snackbarData = snackbarData) }
         }
 
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TooltipTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TooltipTest.kt
index 32ea63a..cc8b1ea 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TooltipTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TooltipTest.kt
@@ -35,6 +35,7 @@
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.MediumTest
 import kotlinx.coroutines.launch
 import org.junit.Rule
@@ -74,6 +75,7 @@
             .assertWidthIsEqualTo(customWidth)
     }
 
+    @FlakyTest(bugId = 264907895)
     @Test
     fun plainTooltip_content_padding() {
         rule.setMaterialContent(lightColorScheme()) {
@@ -92,6 +94,7 @@
             .assertTopPositionInRootIsEqualTo(4.dp)
     }
 
+    @FlakyTest(bugId = 264887805)
     @Test
     fun plainTooltip_behavior() {
         rule.setMaterialContent(lightColorScheme()) {
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DatePickerDialog.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DatePickerDialog.android.kt
new file mode 100644
index 0000000..cee3ad1
--- /dev/null
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DatePickerDialog.android.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredWidth
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.material3.tokens.DialogTokens
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.DialogProperties
+
+/**
+ * <a href="https://m3.material.io/components/date-pickers/overview" class="external" target="_blank">Material Design date picker dialog</a>.
+ *
+ * A dialog for displaying a [DatePicker]. Date pickers let people select a date.
+ *
+ * A sample for displaying a [DatePicker] in a dialog:
+ * @sample androidx.compose.material3.samples.DatePickerDialogSample
+ *
+ * @param onDismissRequest called when the user tries to dismiss the Dialog by clicking outside
+ * or pressing the back button. This is not called when the dismiss button is clicked.
+ * @param confirmButton button which is meant to confirm a proposed action, thus resolving what
+ * triggered the dialog. The dialog does not set up any events for this button, nor does it control
+ * its enablement, so those need to be set up by the caller.
+ * @param modifier the [Modifier] to be applied to this dialog's content.
+ * @param dismissButton button which is meant to dismiss the dialog. The dialog does not set up any
+ * events for this button so they need to be set up by the caller.
+ * @param shape defines the dialog's surface shape as well its shadow
+ * @param tonalElevation when [DatePickerColors.containerColor] is [ColorScheme.surface], a higher
+ * the elevation will result in a darker color in light theme and lighter color in dark theme
+ * @param colors [DatePickerColors] that will be used to resolve the colors used for this date
+ * picker in different states. See [DatePickerDefaults.colors].
+ * @param properties typically platform specific properties to further configure the dialog
+ * @param content the content of the dialog (i.e. a [DatePicker], for example)
+ */
+@ExperimentalMaterial3Api
+@Composable
+fun DatePickerDialog(
+    onDismissRequest: () -> Unit,
+    confirmButton: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    dismissButton: @Composable (() -> Unit)? = null,
+    shape: Shape = DatePickerDefaults.shape,
+    tonalElevation: Dp = DatePickerDefaults.TonalElevation,
+    colors: DatePickerColors = DatePickerDefaults.colors(),
+    properties: DialogProperties = DialogProperties(usePlatformDefaultWidth = false),
+    content: @Composable ColumnScope.() -> Unit
+) {
+    AlertDialog(
+        onDismissRequest = onDismissRequest,
+        modifier = modifier.wrapContentHeight(),
+        properties = properties
+    ) {
+        Surface(
+            // TODO: Use DatePickerModalTokens values for width and height after b/247694457 is
+            //  resolved.
+            modifier = Modifier
+                .requiredWidth(ContainerWidth)
+                .heightIn(max = ContainerHeight),
+            shape = shape,
+            color = colors.containerColor,
+            tonalElevation = tonalElevation,
+        ) {
+            Column(verticalArrangement = Arrangement.SpaceBetween) {
+                content()
+                // Buttons
+                Box(
+                    modifier = Modifier
+                        .align(Alignment.End)
+                        .padding(DialogButtonsPadding)
+                ) {
+                    CompositionLocalProvider(
+                        LocalContentColor provides DialogTokens.ActionLabelTextColor.toColor()
+                    ) {
+                        val textStyle =
+                            MaterialTheme.typography.fromToken(DialogTokens.ActionLabelTextFont)
+                        ProvideTextStyle(value = textStyle) {
+                            AlertDialogFlowRow(
+                                mainAxisSpacing = DialogButtonsMainAxisSpacing,
+                                crossAxisSpacing = DialogButtonsCrossAxisSpacing
+                            ) {
+                                dismissButton?.invoke()
+                                confirmButton()
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+private val DialogButtonsPadding = PaddingValues(bottom = 8.dp, end = 6.dp)
+private val DialogButtonsMainAxisSpacing = 8.dp
+private val DialogButtonsCrossAxisSpacing = 12.dp
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/SearchBar.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/SearchBar.kt
index 3671e1b..4047249 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/SearchBar.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/SearchBar.kt
@@ -401,7 +401,7 @@
     interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
 ) {
     val focusRequester = remember { FocusRequester() }
-    val searchSemantics = getString(Strings.Search)
+    val searchSemantics = getString(Strings.SearchBarSearch)
     val suggestionsAvailableSemantics = getString(Strings.SuggestionsAvailable)
     val textColor = LocalTextStyle.current.color.takeOrElse {
         colors.textColor(enabled).value
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Strings.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Strings.android.kt
index 158c40b..f3ca92b 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Strings.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Strings.android.kt
@@ -36,8 +36,12 @@
         Strings.Dialog -> resources.getString(androidx.compose.material3.R.string.dialog)
         Strings.MenuExpanded -> resources.getString(androidx.compose.material3.R.string.expanded)
         Strings.MenuCollapsed -> resources.getString(androidx.compose.material3.R.string.collapsed)
-        Strings.Dismiss -> resources.getString(androidx.compose.material3.R.string.dismiss)
-        Strings.Search -> resources.getString(androidx.compose.material3.R.string.search)
+        Strings.SnackbarDismiss -> resources.getString(
+            androidx.compose.material3.R.string.snackbar_dismiss
+        )
+        Strings.SearchBarSearch -> resources.getString(
+            androidx.compose.material3.R.string.search_bar_search
+        )
         Strings.SuggestionsAvailable ->
             resources.getString(androidx.compose.material3.R.string.suggestions_available)
         Strings.DatePickerTitle -> resources.getString(
diff --git a/compose/material3/material3/src/androidMain/res/values-af/strings.xml b/compose/material3/material3/src/androidMain/res/values-af/strings.xml
index 8075df8..f17f7e2 100644
--- a/compose/material3/material3/src/androidMain/res/values-af/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-af/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialoog"</string>
     <string name="expanded" msgid="5974471714631304645">"Uitgevou"</string>
     <string name="collapsed" msgid="5389587048670450460">"Ingevou"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Maak toe"</string>
-    <string name="search" msgid="8595876902241072592">"Soek"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Voorstelle hieronder"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Kies datum"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Geselekteerde datum"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-am/strings.xml b/compose/material3/material3/src/androidMain/res/values-am/strings.xml
index ed2e3cf..caebbc7 100644
--- a/compose/material3/material3/src/androidMain/res/values-am/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-am/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"መገናኛ"</string>
     <string name="expanded" msgid="5974471714631304645">"ተዘርግቷል"</string>
     <string name="collapsed" msgid="5389587048670450460">"ተሰብስቧል"</string>
-    <string name="dismiss" msgid="1461218791585306270">"አሰናብት"</string>
-    <string name="search" msgid="8595876902241072592">"ፍለጋ"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"የአስተያየት ጥቆማዎች ከታች"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"ቀን ይምረጡ"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"የተመረጠው ቀን"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ar/strings.xml b/compose/material3/material3/src/androidMain/res/values-ar/strings.xml
index 70f4f05..7d6ce36 100644
--- a/compose/material3/material3/src/androidMain/res/values-ar/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ar/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"مربّع حوار"</string>
     <string name="expanded" msgid="5974471714631304645">"موسَّع"</string>
     <string name="collapsed" msgid="5389587048670450460">"مصغَّر"</string>
-    <string name="dismiss" msgid="1461218791585306270">"إغلاق"</string>
-    <string name="search" msgid="8595876902241072592">"بحث"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"إليك الاقتراحات:"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"اختيار تاريخ"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"التاريخ المحدَّد"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-as/strings.xml b/compose/material3/material3/src/androidMain/res/values-as/strings.xml
index d7f3c72..05565c2 100644
--- a/compose/material3/material3/src/androidMain/res/values-as/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-as/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"ডায়ল’গ"</string>
     <string name="expanded" msgid="5974471714631304645">"বিস্তাৰ কৰা আছে"</string>
     <string name="collapsed" msgid="5389587048670450460">"সংকোচন কৰা আছে"</string>
-    <string name="dismiss" msgid="1461218791585306270">"অগ্ৰাহ্য কৰক"</string>
-    <string name="search" msgid="8595876902241072592">"সন্ধান"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"তলত পৰামৰ্শ দেখুওৱা হৈছে"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"তাৰিখ বাছনি কৰক"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"বাছনি কৰা তাৰিখ"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-az/strings.xml b/compose/material3/material3/src/androidMain/res/values-az/strings.xml
index 3b2fd43..6fe56b0 100644
--- a/compose/material3/material3/src/androidMain/res/values-az/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-az/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialoq"</string>
     <string name="expanded" msgid="5974471714631304645">"Genişləndirilib"</string>
     <string name="collapsed" msgid="5389587048670450460">"Yığcamlaşdırılıb"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Qapadılsın"</string>
-    <string name="search" msgid="8595876902241072592">"Axtarış"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Təkliflər aşağıdadır"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Tarix seçin"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Seçilmiş tarix"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-b+sr+Latn/strings.xml b/compose/material3/material3/src/androidMain/res/values-b+sr+Latn/strings.xml
index c8b3281..c7adcd8 100644
--- a/compose/material3/material3/src/androidMain/res/values-b+sr+Latn/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-b+sr+Latn/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dijalog"</string>
     <string name="expanded" msgid="5974471714631304645">"Prošireno je"</string>
     <string name="collapsed" msgid="5389587048670450460">"Skupljeno je"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Odbaci"</string>
-    <string name="search" msgid="8595876902241072592">"Pretraga"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Predlozi su u nastavku"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Izaberite datum"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Izabrani datum"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-be/strings.xml b/compose/material3/material3/src/androidMain/res/values-be/strings.xml
index 5dcf47d..576a74f 100644
--- a/compose/material3/material3/src/androidMain/res/values-be/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-be/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Дыялогавае акно"</string>
     <string name="expanded" msgid="5974471714631304645">"Разгорнута"</string>
     <string name="collapsed" msgid="5389587048670450460">"Згорнута"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Закрыць"</string>
-    <string name="search" msgid="8595876902241072592">"Пошук"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Прапановы ўнізе"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Выберыце дату"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Выбраная дата"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-bg/strings.xml b/compose/material3/material3/src/androidMain/res/values-bg/strings.xml
index 53c62d3..54d365c 100644
--- a/compose/material3/material3/src/androidMain/res/values-bg/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-bg/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Диалогов прозорец"</string>
     <string name="expanded" msgid="5974471714631304645">"Разгънато"</string>
     <string name="collapsed" msgid="5389587048670450460">"Свито"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Отхвърляне"</string>
-    <string name="search" msgid="8595876902241072592">"Търсене"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Предложенията са по-долу"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Избиране на дата"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Избрана дата"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-bn/strings.xml b/compose/material3/material3/src/androidMain/res/values-bn/strings.xml
index bae5ac5..acf1a6a 100644
--- a/compose/material3/material3/src/androidMain/res/values-bn/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-bn/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"ডায়ালগ বক্স"</string>
     <string name="expanded" msgid="5974471714631304645">"বড় করা হয়েছে"</string>
     <string name="collapsed" msgid="5389587048670450460">"আড়াল করা হয়েছে"</string>
-    <string name="dismiss" msgid="1461218791585306270">"বাতিল করুন"</string>
-    <string name="search" msgid="8595876902241072592">"সার্চ করুন"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"নিচে দেওয়া সাজেশন"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"তারিখ বেছে নিন"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"বেছে নেওয়া তারিখ"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-bs/strings.xml b/compose/material3/material3/src/androidMain/res/values-bs/strings.xml
index 592e753..0d4a8ec 100644
--- a/compose/material3/material3/src/androidMain/res/values-bs/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-bs/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dijaloški okvir"</string>
     <string name="expanded" msgid="5974471714631304645">"Prošireno"</string>
     <string name="collapsed" msgid="5389587048670450460">"Suženo"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Odbacivanje"</string>
-    <string name="search" msgid="8595876902241072592">"Pretraživanje"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Prijedlozi su u nastavku"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Odabir datuma"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Odabrani datum"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ca/strings.xml b/compose/material3/material3/src/androidMain/res/values-ca/strings.xml
index 55f8816..1db45f4 100644
--- a/compose/material3/material3/src/androidMain/res/values-ca/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ca/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Diàleg"</string>
     <string name="expanded" msgid="5974471714631304645">"S\'ha desplegat"</string>
     <string name="collapsed" msgid="5389587048670450460">"S\'ha replegat"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Ignora"</string>
-    <string name="search" msgid="8595876902241072592">"Cerca"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Suggeriments a continuació"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Selecciona la data"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Data seleccionada"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-cs/strings.xml b/compose/material3/material3/src/androidMain/res/values-cs/strings.xml
index a33e105..eaef71a 100644
--- a/compose/material3/material3/src/androidMain/res/values-cs/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-cs/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialogové okno"</string>
     <string name="expanded" msgid="5974471714631304645">"Rozbaleno"</string>
     <string name="collapsed" msgid="5389587048670450460">"Sbaleno"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Zavřít"</string>
-    <string name="search" msgid="8595876902241072592">"Hledat"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Návrh je níže"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Vybrat datum"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Vybrané datum"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-da/strings.xml b/compose/material3/material3/src/androidMain/res/values-da/strings.xml
index 4c9bcda..7f5f7bfa 100644
--- a/compose/material3/material3/src/androidMain/res/values-da/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-da/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialogboks"</string>
     <string name="expanded" msgid="5974471714631304645">"Udvidet"</string>
     <string name="collapsed" msgid="5389587048670450460">"Skjult"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Afvis"</string>
-    <string name="search" msgid="8595876902241072592">"Søg"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Forslag nedenfor"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Vælg dato"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Valgt dato"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-de/strings.xml b/compose/material3/material3/src/androidMain/res/values-de/strings.xml
index d86213e..e211bf1 100644
--- a/compose/material3/material3/src/androidMain/res/values-de/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-de/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialogfeld"</string>
     <string name="expanded" msgid="5974471714631304645">"Maximiert"</string>
     <string name="collapsed" msgid="5389587048670450460">"Minimiert"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Ablehnen"</string>
-    <string name="search" msgid="8595876902241072592">"Suchen"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Vorschläge unten"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Datum auswählen"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Ausgewähltes Datum"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-el/strings.xml b/compose/material3/material3/src/androidMain/res/values-el/strings.xml
index 03afa6e..ce1c470 100644
--- a/compose/material3/material3/src/androidMain/res/values-el/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-el/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Παράθυρο διαλόγου"</string>
     <string name="expanded" msgid="5974471714631304645">"Ανεπτυγμένο"</string>
     <string name="collapsed" msgid="5389587048670450460">"Συμπτυγμένο"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Παράβλεψη"</string>
-    <string name="search" msgid="8595876902241072592">"Αναζήτηση"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Προτάσεις παρακάτω"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Επιλογή ημερομηνίας"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Επιλεγμένη ημερομηνία"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-en-rAU/strings.xml b/compose/material3/material3/src/androidMain/res/values-en-rAU/strings.xml
index d4ff0a6..596e8f24 100644
--- a/compose/material3/material3/src/androidMain/res/values-en-rAU/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-en-rAU/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialogue"</string>
     <string name="expanded" msgid="5974471714631304645">"Expanded"</string>
     <string name="collapsed" msgid="5389587048670450460">"Collapsed"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Dismiss"</string>
-    <string name="search" msgid="8595876902241072592">"Search"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Suggestions below"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Select date"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Selected date"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-en-rCA/strings.xml b/compose/material3/material3/src/androidMain/res/values-en-rCA/strings.xml
index adbcb28..4ee174d 100644
--- a/compose/material3/material3/src/androidMain/res/values-en-rCA/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-en-rCA/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialog"</string>
     <string name="expanded" msgid="5974471714631304645">"Expanded"</string>
     <string name="collapsed" msgid="5389587048670450460">"Collapsed"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Dismiss"</string>
-    <string name="search" msgid="8595876902241072592">"Search"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Suggestions below"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Select date"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Selected date"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-en-rGB/strings.xml b/compose/material3/material3/src/androidMain/res/values-en-rGB/strings.xml
index d4ff0a6..596e8f24 100644
--- a/compose/material3/material3/src/androidMain/res/values-en-rGB/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-en-rGB/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialogue"</string>
     <string name="expanded" msgid="5974471714631304645">"Expanded"</string>
     <string name="collapsed" msgid="5389587048670450460">"Collapsed"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Dismiss"</string>
-    <string name="search" msgid="8595876902241072592">"Search"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Suggestions below"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Select date"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Selected date"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-en-rIN/strings.xml b/compose/material3/material3/src/androidMain/res/values-en-rIN/strings.xml
index d4ff0a6..596e8f24 100644
--- a/compose/material3/material3/src/androidMain/res/values-en-rIN/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-en-rIN/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialogue"</string>
     <string name="expanded" msgid="5974471714631304645">"Expanded"</string>
     <string name="collapsed" msgid="5389587048670450460">"Collapsed"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Dismiss"</string>
-    <string name="search" msgid="8595876902241072592">"Search"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Suggestions below"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Select date"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Selected date"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-en-rXC/strings.xml b/compose/material3/material3/src/androidMain/res/values-en-rXC/strings.xml
index 5e13b3d..d691ec7 100644
--- a/compose/material3/material3/src/androidMain/res/values-en-rXC/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-en-rXC/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‏‎‎‎‎‏‎‏‎‎‏‏‎‎‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‎‏‎‏‎‏‎‏‎‏‎‏‏‎‎‎‎‎‎Dialog‎‏‎‎‏‎"</string>
     <string name="expanded" msgid="5974471714631304645">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‎‏‎‎‏‏‎‎‏‎‏‏‎‎‏‎‏‏‏‏‎‏‏‎‎‏‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‎‏‎‏‏‏‎‎‎‏‎‏‎Expanded‎‏‎‎‏‎"</string>
     <string name="collapsed" msgid="5389587048670450460">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‏‎‏‏‏‎‏‎‏‎‎‎‏‏‎‏‏‏‎‏‏‎‎‏‎‎‏‎‎‎‎‎‏‏‎‎‎‏‎‏‎‎‏‏‎‎‎‏‏‏‎‎‎Collapsed‎‏‎‎‏‎"</string>
-    <string name="dismiss" msgid="1461218791585306270">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‎‎‏‏‏‎‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‏‎‎‎‏‎‏‎‏‎‏‏‎‏‎‎‏‏‏‏‎‎Dismiss‎‏‎‎‏‎"</string>
-    <string name="search" msgid="8595876902241072592">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‏‎‏‎‏‏‎‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‎‎‎‎‎‏‏‏‎‏‎‏‏‏‎‏‎‎‎‎‎Search‎‏‎‎‏‎"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‎‎‎‏‏‏‏‎‎‏‏‏‎‏‎‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‎‏‏‏‎‎‏‏‏‎‏‎‎‏‏‎‏‎‎‏‏‏‏‎‎Suggestions below‎‏‎‎‏‎"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‏‏‎‏‎‎‎‎‎‏‏‎‏‎‏‎‎‎‎‎‎‏‏‎‏‏‎‏‎‎‏‎‎‎‎‎Select date‎‏‎‎‏‎"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‎‎‎‎‏‏‏‎‎‏‏‏‏‎‎‎‎‎‏‏‎‎‏‎‏‏‏‏‎‎‏‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‎‏‎‎‎‎‏‎Selected date‎‏‎‎‏‎"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-es-rUS/strings.xml b/compose/material3/material3/src/androidMain/res/values-es-rUS/strings.xml
index 972a8f1..f9ea7a7 100644
--- a/compose/material3/material3/src/androidMain/res/values-es-rUS/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-es-rUS/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Diálogo"</string>
     <string name="expanded" msgid="5974471714631304645">"Expandido"</string>
     <string name="collapsed" msgid="5389587048670450460">"Contraído"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Descartar"</string>
-    <string name="search" msgid="8595876902241072592">"Buscar"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Sugerencias a continuación"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Seleccionar fecha"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Fecha seleccionada"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-es/strings.xml b/compose/material3/material3/src/androidMain/res/values-es/strings.xml
index 3a866ce..60e5df3 100644
--- a/compose/material3/material3/src/androidMain/res/values-es/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-es/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Cuadro de diálogo"</string>
     <string name="expanded" msgid="5974471714631304645">"Desplegado"</string>
     <string name="collapsed" msgid="5389587048670450460">"Contraído"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Cerrar"</string>
-    <string name="search" msgid="8595876902241072592">"Buscar"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Sugerencias a continuación"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Seleccionar fecha"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Fecha seleccionada"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-et/strings.xml b/compose/material3/material3/src/androidMain/res/values-et/strings.xml
index 9106900..c420378 100644
--- a/compose/material3/material3/src/androidMain/res/values-et/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-et/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialoog"</string>
     <string name="expanded" msgid="5974471714631304645">"Laiendatud"</string>
     <string name="collapsed" msgid="5389587048670450460">"Ahendatud"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Loobu"</string>
-    <string name="search" msgid="8595876902241072592">"Otsing"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Soovitused on allpool"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Valige kuupäev"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Valitud kuupäev"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-eu/strings.xml b/compose/material3/material3/src/androidMain/res/values-eu/strings.xml
index 2c4a279..e3b168c 100644
--- a/compose/material3/material3/src/androidMain/res/values-eu/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-eu/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Leihoa"</string>
     <string name="expanded" msgid="5974471714631304645">"Zabalduta"</string>
     <string name="collapsed" msgid="5389587048670450460">"Tolestuta"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Baztertu"</string>
-    <string name="search" msgid="8595876902241072592">"Bilatu"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Iradokizunak daude behean"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Hautatu data bat"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Hautatutako data"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-fa/strings.xml b/compose/material3/material3/src/androidMain/res/values-fa/strings.xml
index a678350..c2742d5 100644
--- a/compose/material3/material3/src/androidMain/res/values-fa/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-fa/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"کادر گفتگو"</string>
     <string name="expanded" msgid="5974471714631304645">"ازهم بازشده"</string>
     <string name="collapsed" msgid="5389587048670450460">"جمع‌شده"</string>
-    <string name="dismiss" msgid="1461218791585306270">"رد شدن"</string>
-    <string name="search" msgid="8595876902241072592">"جستجو"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"پیشنهادهای زیر"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"انتخاب تاریخ"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"تاریخ انتخابی"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-fi/strings.xml b/compose/material3/material3/src/androidMain/res/values-fi/strings.xml
index 490bd84..54abb11 100644
--- a/compose/material3/material3/src/androidMain/res/values-fi/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-fi/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Valintaikkuna"</string>
     <string name="expanded" msgid="5974471714631304645">"Laajennettu"</string>
     <string name="collapsed" msgid="5389587048670450460">"Tiivistetty"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Hylkää"</string>
-    <string name="search" msgid="8595876902241072592">"Haku"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Ehdotuksia alla"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Valitse päivämäärä"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Valittu päivämäärä"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-fr-rCA/strings.xml b/compose/material3/material3/src/androidMain/res/values-fr-rCA/strings.xml
index e4d5587..34105f8 100644
--- a/compose/material3/material3/src/androidMain/res/values-fr-rCA/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-fr-rCA/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialogue"</string>
     <string name="expanded" msgid="5974471714631304645">"Développé"</string>
     <string name="collapsed" msgid="5389587048670450460">"Réduit"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Fermer"</string>
-    <string name="search" msgid="8595876902241072592">"Recherche"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Suggestions ci-dessous"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Sélectionnez une date"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Date sélectionnée"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-fr/strings.xml b/compose/material3/material3/src/androidMain/res/values-fr/strings.xml
index 0cb1010..6703adf 100644
--- a/compose/material3/material3/src/androidMain/res/values-fr/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-fr/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Boîte de dialogue"</string>
     <string name="expanded" msgid="5974471714631304645">"Développé"</string>
     <string name="collapsed" msgid="5389587048670450460">"Réduit"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Ignorer"</string>
-    <string name="search" msgid="8595876902241072592">"Rechercher"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Suggestions ci-dessous"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Sélectionner une date"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Date sélectionnée"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-gl/strings.xml b/compose/material3/material3/src/androidMain/res/values-gl/strings.xml
index ca68328..61ccee8 100644
--- a/compose/material3/material3/src/androidMain/res/values-gl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-gl/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Cadro de diálogo"</string>
     <string name="expanded" msgid="5974471714631304645">"Despregado"</string>
     <string name="collapsed" msgid="5389587048670450460">"Contraído"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Pechar"</string>
-    <string name="search" msgid="8595876902241072592">"Buscar"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Hai suxestións abaixo"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Selecciona a data"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Data seleccionada"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-gu/strings.xml b/compose/material3/material3/src/androidMain/res/values-gu/strings.xml
index 55997a2..23098b9 100644
--- a/compose/material3/material3/src/androidMain/res/values-gu/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-gu/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"સંવાદ બૉક્સ"</string>
     <string name="expanded" msgid="5974471714631304645">"મોટી કરેલી"</string>
     <string name="collapsed" msgid="5389587048670450460">"નાની કરેલી"</string>
-    <string name="dismiss" msgid="1461218791585306270">"છોડી દો"</string>
-    <string name="search" msgid="8595876902241072592">"શોધો"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"સૂચનો નીચે છે"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"તારીખ પસંદ કરો"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"પસંદ કરેલી તારીખ"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-hi/strings.xml b/compose/material3/material3/src/androidMain/res/values-hi/strings.xml
index d8ded33..ea69e79 100644
--- a/compose/material3/material3/src/androidMain/res/values-hi/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-hi/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"डायलॉग"</string>
     <string name="expanded" msgid="5974471714631304645">"बड़ा किया गया"</string>
     <string name="collapsed" msgid="5389587048670450460">"छोटा किया गया"</string>
-    <string name="dismiss" msgid="1461218791585306270">"खारिज करें"</string>
-    <string name="search" msgid="8595876902241072592">"खोजें"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"सुझाव यहां मौजूद हैं"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"तारीख चुनें"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"चुनी गई तारीख"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-hr/strings.xml b/compose/material3/material3/src/androidMain/res/values-hr/strings.xml
index f2bb389..038c64a 100644
--- a/compose/material3/material3/src/androidMain/res/values-hr/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-hr/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dijaloški okvir"</string>
     <string name="expanded" msgid="5974471714631304645">"Prošireno"</string>
     <string name="collapsed" msgid="5389587048670450460">"Sažeto"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Odbaci"</string>
-    <string name="search" msgid="8595876902241072592">"Pretraživanje"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Prijedlozi su u nastavku"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Odaberite datum"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Odabrani datum"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-hu/strings.xml b/compose/material3/material3/src/androidMain/res/values-hu/strings.xml
index 248168e..8f033c5 100644
--- a/compose/material3/material3/src/androidMain/res/values-hu/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-hu/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Párbeszédablak"</string>
     <string name="expanded" msgid="5974471714631304645">"Kibontva"</string>
     <string name="collapsed" msgid="5389587048670450460">"Összecsukva"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Elvetés"</string>
-    <string name="search" msgid="8595876902241072592">"Keresés"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Javaslatok alább"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Dátum kiválasztása"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Kiválasztott dátum"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-hy/strings.xml b/compose/material3/material3/src/androidMain/res/values-hy/strings.xml
index 34a1312..eea216f 100644
--- a/compose/material3/material3/src/androidMain/res/values-hy/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-hy/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Երկխոսության պատուհան"</string>
     <string name="expanded" msgid="5974471714631304645">"Ծավալված է"</string>
     <string name="collapsed" msgid="5389587048670450460">"Ծալված է"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Փակել"</string>
-    <string name="search" msgid="8595876902241072592">"Որոնել"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Առաջարկները հասանելի են ստորև"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Ընտրեք ամսաթիվը"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Ընտրված ամսաթիվ"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-in/strings.xml b/compose/material3/material3/src/androidMain/res/values-in/strings.xml
index 1edfa94..2361441 100644
--- a/compose/material3/material3/src/androidMain/res/values-in/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-in/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialog"</string>
     <string name="expanded" msgid="5974471714631304645">"Diluaskan"</string>
     <string name="collapsed" msgid="5389587048670450460">"Diciutkan"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Tutup"</string>
-    <string name="search" msgid="8595876902241072592">"Telusuri"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Saran di bawah"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Pilih tanggal"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Tanggal yang dipilih"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-is/strings.xml b/compose/material3/material3/src/androidMain/res/values-is/strings.xml
index 037f206..d5917da 100644
--- a/compose/material3/material3/src/androidMain/res/values-is/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-is/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Gluggi"</string>
     <string name="expanded" msgid="5974471714631304645">"Stækkað"</string>
     <string name="collapsed" msgid="5389587048670450460">"Minnkað"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Hunsa"</string>
-    <string name="search" msgid="8595876902241072592">"Leita"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Tillögur hér fyrir neðan"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Velja dagsetningu"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Valin dagsetning"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-it/strings.xml b/compose/material3/material3/src/androidMain/res/values-it/strings.xml
index 690d277..d24ce76 100644
--- a/compose/material3/material3/src/androidMain/res/values-it/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-it/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Finestra di dialogo"</string>
     <string name="expanded" msgid="5974471714631304645">"Controllo espanso"</string>
     <string name="collapsed" msgid="5389587048670450460">"Controllo compresso"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Chiudi"</string>
-    <string name="search" msgid="8595876902241072592">"Cerca"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Suggerimenti sotto"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Seleziona data"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Data selezionata"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-iw/strings.xml b/compose/material3/material3/src/androidMain/res/values-iw/strings.xml
index 8b653f1..ab2ba8f 100644
--- a/compose/material3/material3/src/androidMain/res/values-iw/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-iw/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"תיבת דו-שיח"</string>
     <string name="expanded" msgid="5974471714631304645">"מורחב"</string>
     <string name="collapsed" msgid="5389587048670450460">"מכווץ"</string>
-    <string name="dismiss" msgid="1461218791585306270">"סגירה"</string>
-    <string name="search" msgid="8595876902241072592">"חיפוש"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"הצעות מופיעות למטה"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"בחירת תאריך"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"התאריך הנבחר"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ja/strings.xml b/compose/material3/material3/src/androidMain/res/values-ja/strings.xml
index a54a9b9..b7f6f60 100644
--- a/compose/material3/material3/src/androidMain/res/values-ja/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ja/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"ダイアログ"</string>
     <string name="expanded" msgid="5974471714631304645">"開いています"</string>
     <string name="collapsed" msgid="5389587048670450460">"閉じています"</string>
-    <string name="dismiss" msgid="1461218791585306270">"閉じる"</string>
-    <string name="search" msgid="8595876902241072592">"検索"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"検索候補は次のとおりです"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"日付を選択します"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"選択した日付"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ka/strings.xml b/compose/material3/material3/src/androidMain/res/values-ka/strings.xml
index 2d049d1..53154d3 100644
--- a/compose/material3/material3/src/androidMain/res/values-ka/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ka/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"დიალოგი"</string>
     <string name="expanded" msgid="5974471714631304645">"გაფართოებულია"</string>
     <string name="collapsed" msgid="5389587048670450460">"ჩაკეცილი"</string>
-    <string name="dismiss" msgid="1461218791585306270">"დახურვა"</string>
-    <string name="search" msgid="8595876902241072592">"ძიება"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"შემოთავაზებები იხილეთ ქვემოთ"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"აირჩიეთ თარიღი"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"არჩეული თარიღი"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-kk/strings.xml b/compose/material3/material3/src/androidMain/res/values-kk/strings.xml
index c919e76..2d22d8d 100644
--- a/compose/material3/material3/src/androidMain/res/values-kk/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-kk/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Диалогтік терезе"</string>
     <string name="expanded" msgid="5974471714631304645">"Жайылды"</string>
     <string name="collapsed" msgid="5389587048670450460">"Жиылды"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Жабу"</string>
-    <string name="search" msgid="8595876902241072592">"Іздеу"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Төмендегі ұсыныстар"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Күн таңдау"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Таңдалған күн"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-km/strings.xml b/compose/material3/material3/src/androidMain/res/values-km/strings.xml
index d9840e1..5b84c88 100644
--- a/compose/material3/material3/src/androidMain/res/values-km/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-km/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"ប្រអប់"</string>
     <string name="expanded" msgid="5974471714631304645">"បាន​ពង្រីក"</string>
     <string name="collapsed" msgid="5389587048670450460">"បាន​បង្រួម"</string>
-    <string name="dismiss" msgid="1461218791585306270">"ច្រានចោល"</string>
-    <string name="search" msgid="8595876902241072592">"ស្វែងរក"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"ការណែនាំខាងក្រោម"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"ជ្រើសរើស​កាលបរិច្ឆេទ"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"កាលបរិច្ឆេទដែលបាន​ជ្រើសរើស"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-kn/strings.xml b/compose/material3/material3/src/androidMain/res/values-kn/strings.xml
index 1d0145e..c19db91 100644
--- a/compose/material3/material3/src/androidMain/res/values-kn/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-kn/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"ಡೈಲಾಗ್"</string>
     <string name="expanded" msgid="5974471714631304645">"ವಿಸ್ತರಿಸಲಾಗಿದೆ"</string>
     <string name="collapsed" msgid="5389587048670450460">"ಕುಗ್ಗಿಸಲಾಗಿದೆ"</string>
-    <string name="dismiss" msgid="1461218791585306270">"ವಜಾಗೊಳಿಸಿ"</string>
-    <string name="search" msgid="8595876902241072592">"ಹುಡುಕಿ"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"ಸಲಹೆಗಳನ್ನು ಕೆಳಗೆ ನೀಡಲಾಗಿದೆ"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"ದಿನಾಂಕವನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"ದಿನಾಂಕವನ್ನು ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ko/strings.xml b/compose/material3/material3/src/androidMain/res/values-ko/strings.xml
index 361bac5..3fe0e18 100644
--- a/compose/material3/material3/src/androidMain/res/values-ko/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ko/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"대화상자"</string>
     <string name="expanded" msgid="5974471714631304645">"펼침"</string>
     <string name="collapsed" msgid="5389587048670450460">"접힘"</string>
-    <string name="dismiss" msgid="1461218791585306270">"닫기"</string>
-    <string name="search" msgid="8595876902241072592">"검색"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"아래의 추천 검색어"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"날짜 선택"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"선택한 날짜"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ky/strings.xml b/compose/material3/material3/src/androidMain/res/values-ky/strings.xml
index 0d3bfa9..bdc280d 100644
--- a/compose/material3/material3/src/androidMain/res/values-ky/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ky/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Диалог"</string>
     <string name="expanded" msgid="5974471714631304645">"Жайылып көрсөтүлдү"</string>
     <string name="collapsed" msgid="5389587048670450460">"Жыйыштырылды"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Четке кагуу"</string>
-    <string name="search" msgid="8595876902241072592">"Издөө"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Сунуштар төмөндө келтирилди"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Күндү тандоо"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Тандалган күн"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-lo/strings.xml b/compose/material3/material3/src/androidMain/res/values-lo/strings.xml
index 3c06f84..a30b4b37 100644
--- a/compose/material3/material3/src/androidMain/res/values-lo/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-lo/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"ກ່ອງໂຕ້ຕອບ"</string>
     <string name="expanded" msgid="5974471714631304645">"ຂະຫຍາຍແລ້ວ"</string>
     <string name="collapsed" msgid="5389587048670450460">"ຫຍໍ້ແລ້ວ"</string>
-    <string name="dismiss" msgid="1461218791585306270">"ປິດໄວ້"</string>
-    <string name="search" msgid="8595876902241072592">"ຊອກຫາ"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"ການແນະນຳຢູ່ຂ້າງລຸ່ມ"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"ເລືອກວັນທີ"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"ວັນທີທີ່ເລືອກໄວ້"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-lt/strings.xml b/compose/material3/material3/src/androidMain/res/values-lt/strings.xml
index da08acd..7da76b7 100644
--- a/compose/material3/material3/src/androidMain/res/values-lt/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-lt/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialogo langas"</string>
     <string name="expanded" msgid="5974471714631304645">"Išskleista"</string>
     <string name="collapsed" msgid="5389587048670450460">"Sutraukta"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Atsisakyti"</string>
-    <string name="search" msgid="8595876902241072592">"Paieška"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Pasiūlymai pateikti toliau"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Pasirinkite datą"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Pasirinkta data"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-lv/strings.xml b/compose/material3/material3/src/androidMain/res/values-lv/strings.xml
index ded3c32..9d54644 100644
--- a/compose/material3/material3/src/androidMain/res/values-lv/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-lv/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialoglodziņš"</string>
     <string name="expanded" msgid="5974471714631304645">"Izvērsts"</string>
     <string name="collapsed" msgid="5389587048670450460">"Sakļauts"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Noraidīt"</string>
-    <string name="search" msgid="8595876902241072592">"Meklēšana"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Tālāk ir sniegti ieteikumi"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Atlasīt datumu"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Atlasītais datums"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-mk/strings.xml b/compose/material3/material3/src/androidMain/res/values-mk/strings.xml
index 473d9ba..43b137d 100644
--- a/compose/material3/material3/src/androidMain/res/values-mk/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-mk/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Дијалог"</string>
     <string name="expanded" msgid="5974471714631304645">"Проширено"</string>
     <string name="collapsed" msgid="5389587048670450460">"Собрано"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Отфрли"</string>
-    <string name="search" msgid="8595876902241072592">"Пребарување"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Предлозите се наведени подолу"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Изберете датум"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Избран датум"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ml/strings.xml b/compose/material3/material3/src/androidMain/res/values-ml/strings.xml
index 894966d..09b2554b 100644
--- a/compose/material3/material3/src/androidMain/res/values-ml/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ml/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"ഡയലോഗ്"</string>
     <string name="expanded" msgid="5974471714631304645">"വിപുലീകരിച്ചത്"</string>
     <string name="collapsed" msgid="5389587048670450460">"ചുരുക്കിയത്"</string>
-    <string name="dismiss" msgid="1461218791585306270">"ഡിസ്‌മിസ് ചെയ്യുക"</string>
-    <string name="search" msgid="8595876902241072592">"തിരയുക"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"നിദ്ദേശങ്ങൾ ചുവടെയുണ്ട്"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"തീയതി തിരഞ്ഞെടുക്കുക"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"തിരഞ്ഞെടുത്ത തീയതി"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-mn/strings.xml b/compose/material3/material3/src/androidMain/res/values-mn/strings.xml
index 6726d22..0270772 100644
--- a/compose/material3/material3/src/androidMain/res/values-mn/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-mn/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Харилцах цонх"</string>
     <string name="expanded" msgid="5974471714631304645">"Дэлгэсэн"</string>
     <string name="collapsed" msgid="5389587048670450460">"Хураасан"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Хаах"</string>
-    <string name="search" msgid="8595876902241072592">"Хайлт"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Доорх зөвлөмжүүд"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Огноо сонгох"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Сонгосон огноо"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-mr/strings.xml b/compose/material3/material3/src/androidMain/res/values-mr/strings.xml
index 843cbab..980c5e8 100644
--- a/compose/material3/material3/src/androidMain/res/values-mr/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-mr/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"डायलॉग"</string>
     <string name="expanded" msgid="5974471714631304645">"विस्तारित केला"</string>
     <string name="collapsed" msgid="5389587048670450460">"कोलॅप्स केला"</string>
-    <string name="dismiss" msgid="1461218791585306270">"डिसमिस करा"</string>
-    <string name="search" msgid="8595876902241072592">"शोधा"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"सूचना खाली आहेत"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"तारीख निवडा"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"निवडलेली तारीख"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ms/strings.xml b/compose/material3/material3/src/androidMain/res/values-ms/strings.xml
index c06ad24..0b7fbbf 100644
--- a/compose/material3/material3/src/androidMain/res/values-ms/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ms/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialog"</string>
     <string name="expanded" msgid="5974471714631304645">"Dikembangkan"</string>
     <string name="collapsed" msgid="5389587048670450460">"Dikuncupkan"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Ketepikan"</string>
-    <string name="search" msgid="8595876902241072592">"Carian"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Cadangan di bawah"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Pilih tarikh"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Tarikh dipilih"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-my/strings.xml b/compose/material3/material3/src/androidMain/res/values-my/strings.xml
index c9f7272..48c1e81 100644
--- a/compose/material3/material3/src/androidMain/res/values-my/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-my/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"ဒိုင်ယာလော့"</string>
     <string name="expanded" msgid="5974471714631304645">"ချဲ့ထားသည်"</string>
     <string name="collapsed" msgid="5389587048670450460">"ခေါက်ထားသည်"</string>
-    <string name="dismiss" msgid="1461218791585306270">"ပယ်ရန်"</string>
-    <string name="search" msgid="8595876902241072592">"ရှာရန်"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"အကြံပြုချက်များ အောက်တွင်ရှိသည်"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"ရက်စွဲရွေးရန်"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"ရွေးထားသည့် ရက်စွဲ"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-nb/strings.xml b/compose/material3/material3/src/androidMain/res/values-nb/strings.xml
index 1b20120..8bec049 100644
--- a/compose/material3/material3/src/androidMain/res/values-nb/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-nb/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialogboks"</string>
     <string name="expanded" msgid="5974471714631304645">"Vises"</string>
     <string name="collapsed" msgid="5389587048670450460">"Skjult"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Lukk"</string>
-    <string name="search" msgid="8595876902241072592">"Søk"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Du finner forslag nedenfor"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Velg dato"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Valgt dato"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ne/strings.xml b/compose/material3/material3/src/androidMain/res/values-ne/strings.xml
index c099435..a53e08a 100644
--- a/compose/material3/material3/src/androidMain/res/values-ne/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ne/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"डायलग"</string>
     <string name="expanded" msgid="5974471714631304645">"एक्स्पान्ड गरियो"</string>
     <string name="collapsed" msgid="5389587048670450460">"कोल्याप्स गरियो"</string>
-    <string name="dismiss" msgid="1461218791585306270">"हटाउनुहोस्"</string>
-    <string name="search" msgid="8595876902241072592">"खोज्नुहोस्"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"सुझावहरू तल दिइएका छन्"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"मिति चयन गर्नुहोस्"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"चयन गरिएको मिति"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-nl/strings.xml b/compose/material3/material3/src/androidMain/res/values-nl/strings.xml
index ca7c80a..e390a5b 100644
--- a/compose/material3/material3/src/androidMain/res/values-nl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-nl/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialoogvenster"</string>
     <string name="expanded" msgid="5974471714631304645">"Uitgevouwen"</string>
     <string name="collapsed" msgid="5389587048670450460">"Samengevouwen"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Sluiten"</string>
-    <string name="search" msgid="8595876902241072592">"Zoeken"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Suggesties hieronder"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Datum selecteren"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Geselecteerde datum"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-or/strings.xml b/compose/material3/material3/src/androidMain/res/values-or/strings.xml
index afac4e3..76a9baa 100644
--- a/compose/material3/material3/src/androidMain/res/values-or/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-or/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"ଡାଏଲଗ"</string>
     <string name="expanded" msgid="5974471714631304645">"ବିସ୍ତାର କରାଯାଇଛି"</string>
     <string name="collapsed" msgid="5389587048670450460">"ସଙ୍କୁଚିତ କରାଯାଇଛି"</string>
-    <string name="dismiss" msgid="1461218791585306270">"ଖାରଜ କରନ୍ତୁ"</string>
-    <string name="search" msgid="8595876902241072592">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"ପରାମର୍ଶ ତଳେ ଦିଆଯାଇଛି"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"ତାରିଖ ଚୟନ କରନ୍ତୁ"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"ଚୟନିତ ତାରିଖ"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-pa/strings.xml b/compose/material3/material3/src/androidMain/res/values-pa/strings.xml
index 0728c6c..d61ea52 100644
--- a/compose/material3/material3/src/androidMain/res/values-pa/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-pa/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"ਵਿੰਡੋ"</string>
     <string name="expanded" msgid="5974471714631304645">"ਵਿਸਤਾਰ ਕੀਤਾ ਗਿਆ"</string>
     <string name="collapsed" msgid="5389587048670450460">"ਸਮੇਟਿਆ ਗਿਆ"</string>
-    <string name="dismiss" msgid="1461218791585306270">"ਖਾਰਜ ਕਰੋ"</string>
-    <string name="search" msgid="8595876902241072592">"ਖੋਜੋ"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"ਸੁਝਾਅ ਹੇਠਾਂ ਹਨ"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"ਤਾਰੀਖ ਚੁਣੋ"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"ਚੁਣੀ ਗਈ ਤਾਰੀਖ"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-pl/strings.xml b/compose/material3/material3/src/androidMain/res/values-pl/strings.xml
index 26ee516..7930b29 100644
--- a/compose/material3/material3/src/androidMain/res/values-pl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-pl/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialog"</string>
     <string name="expanded" msgid="5974471714631304645">"Rozwinięte"</string>
     <string name="collapsed" msgid="5389587048670450460">"Zwinięte"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Zamknij"</string>
-    <string name="search" msgid="8595876902241072592">"Szukaj"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Propozycje znajdziesz poniżej"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Wybierz datę"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Wybrana data"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-pt-rBR/strings.xml b/compose/material3/material3/src/androidMain/res/values-pt-rBR/strings.xml
index 339ab46..1b57ef0 100644
--- a/compose/material3/material3/src/androidMain/res/values-pt-rBR/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-pt-rBR/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Caixa de diálogo"</string>
     <string name="expanded" msgid="5974471714631304645">"Aberto"</string>
     <string name="collapsed" msgid="5389587048670450460">"Fechado"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Dispensar"</string>
-    <string name="search" msgid="8595876902241072592">"Pesquisa"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Sugestões abaixo"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Selecionar data"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Data selecionada"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-pt-rPT/strings.xml b/compose/material3/material3/src/androidMain/res/values-pt-rPT/strings.xml
index ab2f38d..9fb9c1b 100644
--- a/compose/material3/material3/src/androidMain/res/values-pt-rPT/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-pt-rPT/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Caixa de diálogo"</string>
     <string name="expanded" msgid="5974471714631304645">"Expandido"</string>
     <string name="collapsed" msgid="5389587048670450460">"Reduzido"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Ignorar"</string>
-    <string name="search" msgid="8595876902241072592">"Pesquisar"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Sugestões abaixo"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Selecionar data"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Data selecionada"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-pt/strings.xml b/compose/material3/material3/src/androidMain/res/values-pt/strings.xml
index 339ab46..1b57ef0 100644
--- a/compose/material3/material3/src/androidMain/res/values-pt/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-pt/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Caixa de diálogo"</string>
     <string name="expanded" msgid="5974471714631304645">"Aberto"</string>
     <string name="collapsed" msgid="5389587048670450460">"Fechado"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Dispensar"</string>
-    <string name="search" msgid="8595876902241072592">"Pesquisa"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Sugestões abaixo"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Selecionar data"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Data selecionada"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ro/strings.xml b/compose/material3/material3/src/androidMain/res/values-ro/strings.xml
index 60335f1..0afaa0d 100644
--- a/compose/material3/material3/src/androidMain/res/values-ro/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ro/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialog"</string>
     <string name="expanded" msgid="5974471714631304645">"Extins"</string>
     <string name="collapsed" msgid="5389587048670450460">"Restrâns"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Respinge"</string>
-    <string name="search" msgid="8595876902241072592">"Caută"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Sugestii mai jos"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Selectează data"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Data selectată"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ru/strings.xml b/compose/material3/material3/src/androidMain/res/values-ru/strings.xml
index 656224f..0a3e165 100644
--- a/compose/material3/material3/src/androidMain/res/values-ru/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ru/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Диалоговое окно"</string>
     <string name="expanded" msgid="5974471714631304645">"Развернуто"</string>
     <string name="collapsed" msgid="5389587048670450460">"Свернуто"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Закрыть"</string>
-    <string name="search" msgid="8595876902241072592">"Строка поиска"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Подсказки показаны ниже"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Выбор даты"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Выбранная дата"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-si/strings.xml b/compose/material3/material3/src/androidMain/res/values-si/strings.xml
index 9d042e5..bcb0565 100644
--- a/compose/material3/material3/src/androidMain/res/values-si/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-si/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"සංවාදය"</string>
     <string name="expanded" msgid="5974471714631304645">"දිග හරින ලදි"</string>
     <string name="collapsed" msgid="5389587048670450460">"හකුළන ලදි"</string>
-    <string name="dismiss" msgid="1461218791585306270">"අස් කරන්න"</string>
-    <string name="search" msgid="8595876902241072592">"සෙවීම"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"පහත යෝජනා"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"දිනය තෝරන්න"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"තේරූ දිනය"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-sk/strings.xml b/compose/material3/material3/src/androidMain/res/values-sk/strings.xml
index 11cf395..8c85f48 100644
--- a/compose/material3/material3/src/androidMain/res/values-sk/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sk/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialógové okno"</string>
     <string name="expanded" msgid="5974471714631304645">"Rozbalené"</string>
     <string name="collapsed" msgid="5389587048670450460">"Zbalené"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Zavrieť"</string>
-    <string name="search" msgid="8595876902241072592">"Hľadať"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Návrhy sú nižšie"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Vybrať dátum"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Vybraný dátum"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-sl/strings.xml b/compose/material3/material3/src/androidMain/res/values-sl/strings.xml
index d7deaad..91f34b8 100644
--- a/compose/material3/material3/src/androidMain/res/values-sl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sl/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Pogovorno okno"</string>
     <string name="expanded" msgid="5974471714631304645">"Razširjeno"</string>
     <string name="collapsed" msgid="5389587048670450460">"Strnjeno"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Opusti"</string>
-    <string name="search" msgid="8595876902241072592">"Iskanje"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Predlogi so spodaj"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Izbira datuma"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Izbrani datum"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-sq/strings.xml b/compose/material3/material3/src/androidMain/res/values-sq/strings.xml
index 6d9f00d..747e375 100644
--- a/compose/material3/material3/src/androidMain/res/values-sq/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sq/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialogu"</string>
     <string name="expanded" msgid="5974471714631304645">"U zgjerua"</string>
     <string name="collapsed" msgid="5389587048670450460">"U palos"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Hiq"</string>
-    <string name="search" msgid="8595876902241072592">"Kërko"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Sugjerimet më poshtë"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Zgjidh datën"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Data e zgjedhur"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-sr/strings.xml b/compose/material3/material3/src/androidMain/res/values-sr/strings.xml
index 67dba7f..dade0a9 100644
--- a/compose/material3/material3/src/androidMain/res/values-sr/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sr/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Дијалог"</string>
     <string name="expanded" msgid="5974471714631304645">"Проширено је"</string>
     <string name="collapsed" msgid="5389587048670450460">"Скупљено је"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Одбаци"</string>
-    <string name="search" msgid="8595876902241072592">"Претрага"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Предлози су у наставку"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Изаберите датум"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Изабрани датум"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-sv/strings.xml b/compose/material3/material3/src/androidMain/res/values-sv/strings.xml
index 43fd705..f783a1c 100644
--- a/compose/material3/material3/src/androidMain/res/values-sv/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sv/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialogruta"</string>
     <string name="expanded" msgid="5974471714631304645">"Utökad"</string>
     <string name="collapsed" msgid="5389587048670450460">"Komprimerad"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Stäng"</string>
-    <string name="search" msgid="8595876902241072592">"Sök"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Se förslag nedan"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Välj datum"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Valt datum"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-sw/strings.xml b/compose/material3/material3/src/androidMain/res/values-sw/strings.xml
index 3c8c90e..83be738 100644
--- a/compose/material3/material3/src/androidMain/res/values-sw/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sw/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Mazungumzo"</string>
     <string name="expanded" msgid="5974471714631304645">"Imepanuliwa"</string>
     <string name="collapsed" msgid="5389587048670450460">"Imekunjwa"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Ondoa"</string>
-    <string name="search" msgid="8595876902241072592">"Tafuta"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Mapendekezo yaliyo hapa chini"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Chagua tarehe"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Tarehe uliyochagua"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ta/strings.xml b/compose/material3/material3/src/androidMain/res/values-ta/strings.xml
index 86098d5..53cba20 100644
--- a/compose/material3/material3/src/androidMain/res/values-ta/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ta/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"உரையாடல்"</string>
     <string name="expanded" msgid="5974471714631304645">"விரிக்கப்பட்டது"</string>
     <string name="collapsed" msgid="5389587048670450460">"சுருக்கப்பட்டது"</string>
-    <string name="dismiss" msgid="1461218791585306270">"மூடும்"</string>
-    <string name="search" msgid="8595876902241072592">"தேடும்"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"பரிந்துரைகள் கீழே கிடைக்கும்"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"தேதியைத் தேர்ந்தெடுக்கவும்"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"தேர்ந்தெடுக்கப்பட்ட தேதி"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-te/strings.xml b/compose/material3/material3/src/androidMain/res/values-te/strings.xml
index d3899dc..1a79c25 100644
--- a/compose/material3/material3/src/androidMain/res/values-te/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-te/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"డైలాగ్"</string>
     <string name="expanded" msgid="5974471714631304645">"విస్తరించబడింది"</string>
     <string name="collapsed" msgid="5389587048670450460">"కుదించబడింది"</string>
-    <string name="dismiss" msgid="1461218791585306270">"విస్మరించండి"</string>
-    <string name="search" msgid="8595876902241072592">"సెర్చ్ చేయండి"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"సూచనలు దిగువున ఉన్నాయి"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"తేదీని ఎంచుకోండి"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"ఎంచుకున్న తేదీ"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-th/strings.xml b/compose/material3/material3/src/androidMain/res/values-th/strings.xml
index 94a81e4..0c74caa 100644
--- a/compose/material3/material3/src/androidMain/res/values-th/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-th/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"กล่องโต้ตอบ"</string>
     <string name="expanded" msgid="5974471714631304645">"ขยาย"</string>
     <string name="collapsed" msgid="5389587048670450460">"ยุบ"</string>
-    <string name="dismiss" msgid="1461218791585306270">"ปิด"</string>
-    <string name="search" msgid="8595876902241072592">"ค้นหา"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"มีคำแนะนำที่ด้านล่าง"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"เลือกวันที่"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"วันที่ที่เลือก"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-tl/strings.xml b/compose/material3/material3/src/androidMain/res/values-tl/strings.xml
index 873992f..fcf09bd 100644
--- a/compose/material3/material3/src/androidMain/res/values-tl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-tl/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Dialog"</string>
     <string name="expanded" msgid="5974471714631304645">"Naka-expand"</string>
     <string name="collapsed" msgid="5389587048670450460">"Naka-collapse"</string>
-    <string name="dismiss" msgid="1461218791585306270">"I-dismiss"</string>
-    <string name="search" msgid="8595876902241072592">"Maghanap"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Mga suhestyon sa ibaba"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Pumili ng petsa"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Piniling petsa"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-tr/strings.xml b/compose/material3/material3/src/androidMain/res/values-tr/strings.xml
index 404c0f4..1485d39 100644
--- a/compose/material3/material3/src/androidMain/res/values-tr/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-tr/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"İletişim kutusu"</string>
     <string name="expanded" msgid="5974471714631304645">"Genişletilmiş"</string>
     <string name="collapsed" msgid="5389587048670450460">"Daraltılmış"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Kapat"</string>
-    <string name="search" msgid="8595876902241072592">"Arama"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Önerileri aşağıda bulabilirsiniz"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Tarih seç"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Seçilen tarih"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-uk/strings.xml b/compose/material3/material3/src/androidMain/res/values-uk/strings.xml
index 9e5cbc2..d7c398b8 100644
--- a/compose/material3/material3/src/androidMain/res/values-uk/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-uk/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Вікно"</string>
     <string name="expanded" msgid="5974471714631304645">"Розгорнуто"</string>
     <string name="collapsed" msgid="5389587048670450460">"Згорнуто"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Закрити"</string>
-    <string name="search" msgid="8595876902241072592">"Пошук"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Підказки внизу"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Вибрати дату"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Вибрана дата"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ur/strings.xml b/compose/material3/material3/src/androidMain/res/values-ur/strings.xml
index 8e99d91..2bc5202 100644
--- a/compose/material3/material3/src/androidMain/res/values-ur/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ur/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"ڈائلاگ"</string>
     <string name="expanded" msgid="5974471714631304645">"پھیلایا گیا"</string>
     <string name="collapsed" msgid="5389587048670450460">"سکیڑا گیا"</string>
-    <string name="dismiss" msgid="1461218791585306270">"برخاست کریں"</string>
-    <string name="search" msgid="8595876902241072592">"تلاش کریں"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"تلاش کی تجاویز نیچے دستیاب ہیں"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"تاریخ منتخب کریں"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"منتخب کردہ تاریخ"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-uz/strings.xml b/compose/material3/material3/src/androidMain/res/values-uz/strings.xml
index 615b41b..51cb568 100644
--- a/compose/material3/material3/src/androidMain/res/values-uz/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-uz/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Muloqot oynasi"</string>
     <string name="expanded" msgid="5974471714631304645">"Yoyilgan"</string>
     <string name="collapsed" msgid="5389587048670450460">"Yigʻilgan"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Yopish"</string>
-    <string name="search" msgid="8595876902241072592">"Qidiruv"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Takliflar quyida"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Sanani tanlang"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Tanlangan sana"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-vi/strings.xml b/compose/material3/material3/src/androidMain/res/values-vi/strings.xml
index 6f28932..c39a5bc 100644
--- a/compose/material3/material3/src/androidMain/res/values-vi/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-vi/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Hộp thoại"</string>
     <string name="expanded" msgid="5974471714631304645">"Đã mở rộng"</string>
     <string name="collapsed" msgid="5389587048670450460">"Đã thu gọn"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Đóng"</string>
-    <string name="search" msgid="8595876902241072592">"Tìm kiếm"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Các đề xuất ở bên dưới"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Chọn ngày"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Ngày đã chọn"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-zh-rCN/strings.xml b/compose/material3/material3/src/androidMain/res/values-zh-rCN/strings.xml
index f87282a..e33dea1 100644
--- a/compose/material3/material3/src/androidMain/res/values-zh-rCN/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-zh-rCN/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"对话框"</string>
     <string name="expanded" msgid="5974471714631304645">"已展开"</string>
     <string name="collapsed" msgid="5389587048670450460">"已收起"</string>
-    <string name="dismiss" msgid="1461218791585306270">"关闭"</string>
-    <string name="search" msgid="8595876902241072592">"搜索"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"以下是搜索建议"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"选择日期"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"选定的日期"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-zh-rHK/strings.xml b/compose/material3/material3/src/androidMain/res/values-zh-rHK/strings.xml
index 8f77fd6..033e623 100644
--- a/compose/material3/material3/src/androidMain/res/values-zh-rHK/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-zh-rHK/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"對話框"</string>
     <string name="expanded" msgid="5974471714631304645">"展開咗"</string>
     <string name="collapsed" msgid="5389587048670450460">"合埋咗"</string>
-    <string name="dismiss" msgid="1461218791585306270">"關閉"</string>
-    <string name="search" msgid="8595876902241072592">"搜尋"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"建議如下"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"選取日期"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"所選日期"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-zh-rTW/strings.xml b/compose/material3/material3/src/androidMain/res/values-zh-rTW/strings.xml
index 3373d5c..ca1ce24 100644
--- a/compose/material3/material3/src/androidMain/res/values-zh-rTW/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-zh-rTW/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"對話方塊"</string>
     <string name="expanded" msgid="5974471714631304645">"已展開"</string>
     <string name="collapsed" msgid="5389587048670450460">"已收合"</string>
-    <string name="dismiss" msgid="1461218791585306270">"關閉"</string>
-    <string name="search" msgid="8595876902241072592">"搜尋"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"建議如下"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"選取日期"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"所選日期"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-zu/strings.xml b/compose/material3/material3/src/androidMain/res/values-zu/strings.xml
index a2276aa..68ea064 100644
--- a/compose/material3/material3/src/androidMain/res/values-zu/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-zu/strings.xml
@@ -20,8 +20,6 @@
     <string name="dialog" msgid="4057925834421392736">"Ibhokisi"</string>
     <string name="expanded" msgid="5974471714631304645">"Kunwetshiwe"</string>
     <string name="collapsed" msgid="5389587048670450460">"Kugoqiwe"</string>
-    <string name="dismiss" msgid="1461218791585306270">"Chitha"</string>
-    <string name="search" msgid="8595876902241072592">"Sesha"</string>
     <string name="suggestions_available" msgid="7189888345201419934">"Iziphakamiso ngezansi"</string>
     <string name="date_picker_title" msgid="9208721003668059792">"Khetha usuku"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Khetha usuku"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values/strings.xml b/compose/material3/material3/src/androidMain/res/values/strings.xml
index c75989b..9284a12 100644
--- a/compose/material3/material3/src/androidMain/res/values/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values/strings.xml
@@ -22,10 +22,10 @@
     <string name="expanded">Expanded</string>
     <!-- Spoken description of collapsed state of an expandable item -->
     <string name="collapsed">Collapsed</string>
-    <!-- Spoken description of a generic dismiss action item -->
-    <string name="dismiss">Dismiss</string>
+    <!-- Spoken description of a snackbar dismiss action -->
+    <string name="snackbar_dismiss">Dismiss</string>
     <!-- Spoken description of a search bar -->
-    <string name="search">Search</string>
+    <string name="search_bar_search">Search</string>
     <!-- Spoken description when search suggestions are available -->
     <string name="suggestions_available">Suggestions below</string>
     <!-- Spoken description of date picker and date input items -->
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 098c52c..7393f4d 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
@@ -79,11 +79,13 @@
 import androidx.compose.ui.draw.rotate
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.semantics.clearAndSetSemantics
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import java.lang.Integer.max
@@ -91,11 +93,11 @@
 import kotlinx.coroutines.launch
 
 // TODO: External preview image.
-// TODO: Update the docs to reference the upcoming DatePickerDialog.
 /**
  * <a href="https://m3.material.io/components/date-pickers/overview" class="external" target="_blank">Material Design date picker</a>.
  *
  * Date pickers let people select a date and preferably should be embedded into Dialogs.
+ * See [DatePickerDialog].
  *
  * A simple DatePicker looks like:
  * @sample androidx.compose.material3.samples.DatePickerSample
@@ -414,6 +416,12 @@
 
     /** The range of years for the date picker dialogs. */
     val YearRange: IntRange = IntRange(1900, 2100)
+
+    /** The default tonal elevation used for [DatePickerDialog]. */
+    val TonalElevation: Dp = DatePickerModalTokens.ContainerElevation
+
+    /** The default shape for date picker dialogs. */
+    val shape: Shape @Composable get() = DatePickerModalTokens.ContainerShape.toShape()
 }
 
 /**
@@ -648,7 +656,7 @@
     val coroutineScope = rememberCoroutineScope()
     Column(
         modifier = modifier
-            .sizeIn(minWidth = ContainerWidth, minHeight = ContainerHeight)
+            .sizeIn(minWidth = ContainerWidth)
             .padding(DatePickerHorizontalPadding)
     ) {
         DatePickerHeader(
@@ -712,6 +720,13 @@
             ) {
                 Column {
                     YearPicker(
+                        // Keep the height the same as the monthly calendar + weekdays height, and
+                        // take into account the thickness of the divider that will be composed
+                        // below it.
+                        modifier = Modifier.requiredHeight(
+                            RecommendedSizeForAccessibility * (MaxCalendarRows + 1) -
+                                DividerDefaults.Thickness
+                        ),
                         onYearSelected = { year ->
                             // Switch back to the monthly calendar and scroll to the selected year.
                             yearPickerVisible = !yearPickerVisible
@@ -1010,6 +1025,7 @@
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun YearPicker(
+    modifier: Modifier,
     onYearSelected: (year: Int) -> Unit,
     colors: DatePickerColors,
     datePickerState: DatePickerState
@@ -1027,12 +1043,15 @@
                     0, displayedYear - datePickerState.yearRange.first - YearsInRow
                 )
             )
+        // Match the years container color to any elevated surface color that is composed under it.
+        val containerColor = if (colors.containerColor == MaterialTheme.colorScheme.surface) {
+            MaterialTheme.colorScheme.surfaceColorAtElevation(LocalAbsoluteTonalElevation.current)
+        } else {
+            colors.containerColor
+        }
         LazyVerticalGrid(
             columns = GridCells.Fixed(YearsInRow),
-            // Keep the height the same as the monthly calendar height + weekdays height.
-            modifier = Modifier
-                .requiredHeight(RecommendedSizeForAccessibility * (MaxCalendarRows + 1))
-                .background(colors.containerColor),
+            modifier = modifier.background(containerColor),
             state = lazyGridState,
             horizontalArrangement = Arrangement.SpaceEvenly,
             verticalArrangement = Arrangement.spacedBy(YearsVerticalPadding)
@@ -1204,6 +1223,10 @@
     return formatter.format(this)
 }
 
+// TODO: Remove after b/247694457 for updating the tokens is resolved.
+internal val ContainerWidth = 360.dp
+internal val ContainerHeight = 568.dp
+
 internal val MonthYearHeight = 56.dp
 internal val DatePickerHorizontalPadding = PaddingValues(horizontal = 12.dp)
 internal val HeaderPadding = PaddingValues(
@@ -1211,20 +1234,12 @@
     top = 16.dp,
     bottom = 12.dp
 )
-internal val CalendarMonthSubheadPadding = PaddingValues(
-    start = 12.dp,
-    top = 20.dp,
-    bottom = 8.dp
-)
+
 private val YearsVerticalPadding = 16.dp
 
 private const val MaxCalendarRows = 6
 private const val YearsInRow: Int = 3
 
-// TODO: Remove after b/247694457 for updating the tokens is resolved.
-private val ContainerWidth = 360.dp
-private val ContainerHeight = 568.dp
-
 // TODO: Remove after b/251240936 for updating the typography is resolved.
 private val WeekdaysLabelTextFont = TypographyKeyTokens.BodyLarge
 private val DateLabelTextFont = TypographyKeyTokens.BodyLarge
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ListItem.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ListItem.kt
index e05c5dc..6f5cb42c 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ListItem.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ListItem.kt
@@ -34,6 +34,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 
@@ -297,8 +298,9 @@
     ) {
         Row(
             modifier = Modifier
-            .heightIn(min = minHeight)
-            .padding(paddingValues),
+                .heightIn(min = minHeight)
+                .padding(paddingValues)
+                .semantics(mergeDescendants = true) {},
             content = content
         )
     }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
index c66c26e..f10ff73 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/NavigationDrawer.kt
@@ -57,9 +57,7 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.input.ScrollContainerInfo
 import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.input.provideScrollContainerInfo
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
@@ -266,15 +264,6 @@
 
     val anchors = mapOf(minValue to DrawerValue.Closed, maxValue to DrawerValue.Open)
     val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
-
-    val containerInfo = remember(gesturesEnabled) {
-        object : ScrollContainerInfo {
-            override fun canScrollHorizontally() = gesturesEnabled
-
-            override fun canScrollVertically() = false
-        }
-    }
-
     Box(
         modifier
             .fillMaxSize()
@@ -288,7 +277,6 @@
                 velocityThreshold = DrawerVelocityThreshold,
                 resistance = null
             )
-            .provideScrollContainerInfo(containerInfo)
     ) {
         Box {
             content()
@@ -369,14 +357,6 @@
 
     val anchors = mapOf(minValue to DrawerValue.Closed, maxValue to DrawerValue.Open)
     val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
-    val containerInfo = remember(gesturesEnabled) {
-        object : ScrollContainerInfo {
-            override fun canScrollHorizontally() = gesturesEnabled
-
-            override fun canScrollVertically() = false
-        }
-    }
-
     Box(
         modifier.swipeable(
             state = drawerState.swipeableState,
@@ -387,7 +367,7 @@
             reverseDirection = isRtl,
             velocityThreshold = DrawerVelocityThreshold,
             resistance = null
-        ).provideScrollContainerInfo(containerInfo)
+        )
     ) {
         Layout(content = {
             Box(Modifier.semantics {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
index c56363f..e9b3e91 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
@@ -39,14 +39,10 @@
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.interaction.PressInteraction
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxScope
-import androidx.compose.foundation.layout.BoxWithConstraints
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.heightIn
-import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.requiredSizeIn
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.widthIn
@@ -66,7 +62,6 @@
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
 import androidx.compose.ui.draw.shadow
@@ -84,14 +79,12 @@
 import androidx.compose.ui.input.pointer.positionChange
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.layoutId
-import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.disabled
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.setProgress
-import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
@@ -174,20 +167,20 @@
         onValueChangeFinished = onValueChangeFinished,
         colors = colors,
         interactionSource = interactionSource,
-        thumb = remember(interactionSource, colors, enabled) { {
+        thumb = {
             SliderDefaults.Thumb(
                 interactionSource = interactionSource,
                 colors = colors,
                 enabled = enabled
             )
-        } },
-        track = remember(colors, enabled) { { sliderPositions ->
+        },
+        track = { sliderPositions ->
             SliderDefaults.Track(
                 colors = colors,
                 enabled = enabled,
                 sliderPositions = sliderPositions
             )
-        } }
+        }
     )
 }
 
@@ -257,13 +250,13 @@
         colors = colors,
         interactionSource = interactionSource,
         thumb = thumb,
-        track = remember(colors, enabled) { { sliderPositions ->
+        track = { sliderPositions ->
             SliderDefaults.Track(
                 colors = colors,
                 enabled = enabled,
                 sliderPositions = sliderPositions
             )
-        } }
+        }
     )
 }
 
@@ -322,14 +315,13 @@
     onValueChangeFinished: (() -> Unit)? = null,
     colors: SliderColors = SliderDefaults.colors(),
     interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
-    thumb: @Composable (SliderPositions) -> Unit =
-        remember(interactionSource, colors, enabled) { {
-            SliderDefaults.Thumb(
-                interactionSource = interactionSource,
-                colors = colors,
-                enabled = enabled
-            )
-        } }
+    thumb: @Composable (SliderPositions) -> Unit = {
+        SliderDefaults.Thumb(
+            interactionSource = interactionSource,
+            colors = colors,
+            enabled = enabled
+        )
+    }
 ) {
     require(steps >= 0) { "steps should be >= 0" }
 
@@ -348,12 +340,17 @@
 }
 
 /**
- * <a href="https://m3.material.io/components/sliders/overview" class="external" target="_blank">Material Design Range slider</a>.
+ * Material Design Range slider
  *
  * Range Sliders expand upon [Slider] using the same concepts but allow the user to select 2 values.
  *
  * The two values are still bounded by the value range but they also cannot cross each other.
  *
+ * It uses the provided startThumb for the slider's start thumb and endThumb for the
+ * slider's end thumb. It also uses the provided track for the slider's track. If nothing is
+ * passed for these parameters, it will use [SliderDefaults.Thumb] and [SliderDefaults.Track]
+ * for the thumbs and track.
+ *
  * Use continuous Range Sliders to allow users to make meaningful selections that don’t
  * require a specific values:
  *
@@ -364,6 +361,10 @@
  *
  * @sample androidx.compose.material3.samples.StepRangeSliderSample
  *
+ * A custom start/end thumb and track can be provided:
+ *
+ * @sample androidx.compose.material3.samples.RangeSliderWithCustomComponents
+ *
  * @param value current values of the RangeSlider. If either value is outside of [valueRange]
  * provided, it will be coerced to this range.
  * @param onValueChange lambda in which values should be updated
@@ -379,6 +380,21 @@
  * know when the user has completed selecting a new value by ending a drag or a click.
  * @param colors [SliderColors] that will be used to determine the color of the Range Slider
  * parts in different state. See [SliderDefaults.colors] to customize.
+ * @param startInteractionSource the [MutableInteractionSource] representing the stream of
+ * [Interaction]s for the start thumb. You can create and pass in your own
+ * `remember`ed instance to observe.
+ * @param endInteractionSource the [MutableInteractionSource] representing the stream of
+ * [Interaction]s for the end thumb. You can create and pass in your own
+ * `remember`ed instance to observe.
+ * @param startThumb the start thumb to be displayed on the Range Slider. The lambda receives a
+ * [SliderPositions] which is used to obtain the current active track and the tick
+ * positions if the range slider is discrete.
+ * @param endThumb the end thumb to be displayed on the Range Slider. The lambda receives a
+ * [SliderPositions] which is used to obtain the current active track and the tick
+ * positions if the range slider is discrete.
+ * @param track the track to be displayed on the range slider, it is placed underneath the thumb.
+ * The lambda receives a [SliderPositions] which is used to obtain the current active track and the
+ * tick positions if the range slider is discrete.
  */
 @Composable
 @ExperimentalMaterial3Api
@@ -388,204 +404,447 @@
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
     valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
+    onValueChangeFinished: (() -> Unit)? = null,
+    colors: SliderColors = SliderDefaults.colors(),
+    startInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+    endInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+    startThumb: @Composable (SliderPositions) -> Unit = {
+        SliderDefaults.Thumb(
+            interactionSource = startInteractionSource,
+            colors = colors,
+            enabled = enabled
+        )
+    },
+    endThumb: @Composable (SliderPositions) -> Unit = {
+        SliderDefaults.Thumb(
+            interactionSource = endInteractionSource,
+            colors = colors,
+            enabled = enabled
+        )
+    },
+    track: @Composable (SliderPositions) -> Unit = { sliderPositions ->
+            SliderDefaults.Track(
+                colors = colors,
+                enabled = enabled,
+                sliderPositions = sliderPositions
+            )
+    },
     /*@IntRange(from = 0)*/
     steps: Int = 0,
-    onValueChangeFinished: (() -> Unit)? = null,
-    colors: SliderColors = SliderDefaults.colors()
 ) {
-    val startInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() }
-    val endInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() }
-
     require(steps >= 0) { "steps should be >= 0" }
-    val onValueChangeState = rememberUpdatedState<(ClosedFloatingPointRange<Float>) -> Unit> {
+
+    RangeSliderImpl(
+        modifier = modifier,
+        value = value,
+        onValueChange = onValueChange,
+        enabled = enabled,
+        valueRange = valueRange,
+        steps = steps,
+        onValueChangeFinished = onValueChangeFinished,
+        startInteractionSource = startInteractionSource,
+        endInteractionSource = endInteractionSource,
+        startThumb = startThumb,
+        endThumb = endThumb,
+        track = track
+    )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun SliderImpl(
+    modifier: Modifier,
+    enabled: Boolean,
+    interactionSource: MutableInteractionSource,
+    onValueChange: (Float) -> Unit,
+    onValueChangeFinished: (() -> Unit)?,
+    steps: Int,
+    value: Float,
+    valueRange: ClosedFloatingPointRange<Float>,
+    thumb: @Composable (SliderPositions) -> Unit,
+    track: @Composable (SliderPositions) -> Unit
+) {
+    val onValueChangeState = rememberUpdatedState<(Float) -> Unit> {
         if (it != value) {
             onValueChange(it)
         }
     }
+
     val tickFractions = remember(steps) {
         stepsToTickFractions(steps)
     }
 
-    BoxWithConstraints(
-        modifier = modifier
-            .minimumInteractiveComponentSize()
-            .requiredSizeIn(minWidth = ThumbWidth * 2, minHeight = ThumbHeight * 2)
-    ) {
-        val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
-        val widthPx = constraints.maxWidth.toFloat()
-        val maxPx: Float
-        val minPx: Float
+    val thumbWidth = remember { mutableStateOf(ThumbWidth.value) }
+    val totalWidth = remember { mutableStateOf(0) }
 
-        with(LocalDensity.current) {
-            maxPx = widthPx - ThumbWidth.toPx() / 2
-            minPx = ThumbWidth.toPx() / 2
+    fun scaleToUserValue(minPx: Float, maxPx: Float, offset: Float) =
+        scale(minPx, maxPx, offset, valueRange.start, valueRange.endInclusive)
+
+    fun scaleToOffset(minPx: Float, maxPx: Float, userValue: Float) =
+        scale(valueRange.start, valueRange.endInclusive, userValue, minPx, maxPx)
+
+    val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
+    val rawOffset = remember { mutableStateOf(scaleToOffset(0f, 0f, value)) }
+    val pressOffset = remember { mutableStateOf(0f) }
+    val coerced = value.coerceIn(valueRange.start, valueRange.endInclusive)
+
+    val positionFraction = calcFraction(valueRange.start, valueRange.endInclusive, coerced)
+    val sliderPositions = remember {
+        SliderPositions(0f..positionFraction, tickFractions)
+    }
+    sliderPositions.activeRange = 0f..positionFraction
+    sliderPositions.tickFractions = tickFractions
+
+    val draggableState = remember(valueRange) {
+        SliderDraggableState {
+            val maxPx = max(totalWidth.value - thumbWidth.value / 2, 0f)
+            val minPx = min(thumbWidth.value / 2, maxPx)
+            rawOffset.value = (rawOffset.value + it + pressOffset.value)
+            pressOffset.value = 0f
+            val offsetInTrack = snapValueToTick(rawOffset.value, tickFractions, minPx, maxPx)
+            onValueChangeState.value.invoke(scaleToUserValue(minPx, maxPx, offsetInTrack))
         }
+    }
 
-        fun scaleToUserValue(offset: ClosedFloatingPointRange<Float>) =
-            scale(minPx, maxPx, offset, valueRange.start, valueRange.endInclusive)
-
-        fun scaleToOffset(userValue: Float) =
-            scale(valueRange.start, valueRange.endInclusive, userValue, minPx, maxPx)
-
-        val rawOffsetStart = remember { mutableStateOf(scaleToOffset(value.start)) }
-        val rawOffsetEnd = remember { mutableStateOf(scaleToOffset(value.endInclusive)) }
-
-        val gestureEndAction = rememberUpdatedState<(Boolean) -> Unit> {
+    val gestureEndAction = rememberUpdatedState {
+        if (!draggableState.isDragging) {
+            // check isDragging in case the change is still in progress (touch -> drag case)
             onValueChangeFinished?.invoke()
         }
+    }
 
-        val onDrag = rememberUpdatedState<(Boolean, Float) -> Unit> { isStart, offset ->
-            val offsetRange = if (isStart) {
-                rawOffsetStart.value = (rawOffsetStart.value + offset)
-                rawOffsetEnd.value = scaleToOffset(value.endInclusive)
-                val offsetEnd = rawOffsetEnd.value
-                var offsetStart = rawOffsetStart.value.coerceIn(minPx, offsetEnd)
-                offsetStart = snapValueToTick(offsetStart, tickFractions, minPx, maxPx)
-                offsetStart..offsetEnd
-            } else {
-                rawOffsetEnd.value = (rawOffsetEnd.value + offset)
-                rawOffsetStart.value = scaleToOffset(value.start)
-                val offsetStart = rawOffsetStart.value
-                var offsetEnd = rawOffsetEnd.value.coerceIn(offsetStart, maxPx)
-                offsetEnd = snapValueToTick(offsetEnd, tickFractions, minPx, maxPx)
-                offsetStart..offsetEnd
-            }
+    val press = Modifier.sliderTapModifier(
+        draggableState,
+        interactionSource,
+        totalWidth.value,
+        isRtl,
+        rawOffset,
+        gestureEndAction,
+        pressOffset,
+        enabled
+    )
 
-            onValueChangeState.value.invoke(scaleToUserValue(offsetRange))
+    val drag = Modifier.draggable(
+        orientation = Orientation.Horizontal,
+        reverseDirection = isRtl,
+        enabled = enabled,
+        interactionSource = interactionSource,
+        onDragStopped = { _ -> gestureEndAction.value.invoke() },
+        startDragImmediately = draggableState.isDragging,
+        state = draggableState
+    )
+
+    Layout(
+        {
+            Box(modifier = Modifier.layoutId(SliderComponents.THUMB)) { thumb(sliderPositions) }
+            Box(modifier = Modifier.layoutId(SliderComponents.TRACK)) { track(sliderPositions) }
+        },
+        modifier = modifier
+            .minimumInteractiveComponentSize()
+            .requiredSizeIn(
+                minWidth = SliderTokens.HandleWidth,
+                minHeight = SliderTokens.HandleHeight
+            )
+            .sliderSemantics(
+                value,
+                enabled,
+                onValueChange,
+                onValueChangeFinished,
+                valueRange,
+                steps
+            )
+            .focusable(enabled, interactionSource)
+            .then(press)
+            .then(drag)
+    ) { measurables, constraints ->
+
+        val thumbPlaceable = measurables.first {
+            it.layoutId == SliderComponents.THUMB
+        }.measure(constraints)
+
+        val maxTrackWidth = constraints.maxWidth - thumbPlaceable.width
+        val trackPlaceable = measurables.first {
+            it.layoutId == SliderComponents.TRACK
+        }.measure(
+            constraints.copy(
+                minWidth = 0,
+                maxWidth = maxTrackWidth,
+                minHeight = 0
+            )
+        )
+
+        val sliderWidth = thumbPlaceable.width + trackPlaceable.width
+        val sliderHeight = max(trackPlaceable.height, thumbPlaceable.height)
+
+        thumbWidth.value = thumbPlaceable.width.toFloat()
+        totalWidth.value = sliderWidth
+
+        val trackOffsetX = thumbPlaceable.width / 2
+        val thumbOffsetX = ((trackPlaceable.width) * positionFraction).roundToInt()
+        val trackOffsetY = (sliderHeight - trackPlaceable.height) / 2
+        val thumbOffsetY = (sliderHeight - thumbPlaceable.height) / 2
+
+        layout(
+            sliderWidth,
+            sliderHeight
+        ) {
+            trackPlaceable.placeRelative(
+                trackOffsetX,
+                trackOffsetY
+            )
+            thumbPlaceable.placeRelative(
+                thumbOffsetX,
+                thumbOffsetY
+            )
         }
-
-        val pressDrag = Modifier.rangeSliderPressDragModifier(
-            startInteractionSource,
-            endInteractionSource,
-            rawOffsetStart,
-            rawOffsetEnd,
-            enabled,
-            isRtl,
-            widthPx,
-            valueRange,
-            gestureEndAction,
-            onDrag,
-        )
-        // The positions of the thumbs are dependant on each other.
-        val coercedStart = value.start.coerceIn(valueRange.start, value.endInclusive)
-        val coercedEnd = value.endInclusive.coerceIn(value.start, valueRange.endInclusive)
-        val fractionStart = calcFraction(valueRange.start, valueRange.endInclusive, coercedStart)
-        val fractionEnd = calcFraction(valueRange.start, valueRange.endInclusive, coercedEnd)
-        val startSteps = floor(steps * fractionEnd).toInt()
-        val endSteps = floor(steps * (1f - fractionStart)).toInt()
-
-        val startThumbSemantics = Modifier.sliderSemantics(
-            coercedStart,
-            enabled,
-            { value -> onValueChangeState.value.invoke(value..coercedEnd) },
-            onValueChangeFinished,
-            valueRange.start..coercedEnd,
-            startSteps
-        )
-        val endThumbSemantics = Modifier.sliderSemantics(
-            coercedEnd,
-            enabled,
-            { value -> onValueChangeState.value.invoke(coercedStart..value) },
-            onValueChangeFinished,
-            coercedStart..valueRange.endInclusive,
-            endSteps
-        )
-
-        RangeSliderImpl(
-            enabled,
-            fractionStart,
-            fractionEnd,
-            tickFractions,
-            colors,
-            maxPx - minPx,
-            startInteractionSource,
-            endInteractionSource,
-            modifier = pressDrag,
-            startThumbSemantics,
-            endThumbSemantics
-        )
     }
 }
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun RangeSliderImpl(
+    modifier: Modifier,
+    value: ClosedFloatingPointRange<Float>,
+    onValueChange: (ClosedFloatingPointRange<Float>) -> Unit,
     enabled: Boolean,
-    positionFractionStart: Float,
-    positionFractionEnd: Float,
-    tickFractions: FloatArray,
-    colors: SliderColors,
-    width: Float,
+    valueRange: ClosedFloatingPointRange<Float>,
+    steps: Int = 0,
+    onValueChangeFinished: (() -> Unit)?,
     startInteractionSource: MutableInteractionSource,
     endInteractionSource: MutableInteractionSource,
-    modifier: Modifier,
-    startThumbSemantics: Modifier,
-    endThumbSemantics: Modifier
+    startThumb: @Composable ((SliderPositions) -> Unit),
+    endThumb: @Composable ((SliderPositions) -> Unit),
+    track: @Composable ((SliderPositions) -> Unit)
 ) {
-    val startContentDescription = getString(Strings.SliderRangeStart)
-    val endContentDescription = getString(Strings.SliderRangeEnd)
-    Box(modifier.then(DefaultSliderConstraints)) {
-        val trackStrokeWidth: Float
-        val widthDp: Dp
-        with(LocalDensity.current) {
-            trackStrokeWidth = TrackHeight.toPx()
-            widthDp = width.toDp()
+    val onValueChangeState = rememberUpdatedState<(ClosedFloatingPointRange<Float>) -> Unit> {
+        if (it != value) {
+            onValueChange(it)
+        }
+    }
+
+    val tickFractions = remember(steps) {
+        stepsToTickFractions(steps)
+    }
+
+    var startThumbWidth by remember { mutableStateOf(ThumbWidth.value) }
+    var endThumbWidth by remember { mutableStateOf(ThumbWidth.value) }
+    var totalWidth by remember { mutableStateOf(0) }
+
+    val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
+
+    // scales range offset from within minPx..maxPx to within valueRange.start..valueRange.end
+    fun scaleToUserValue(minPx: Float, maxPx: Float, offset: ClosedFloatingPointRange<Float>) =
+        scale(minPx, maxPx, offset, valueRange.start, valueRange.endInclusive)
+
+    // scales float userValue within valueRange.start..valueRange.end to within minPx..maxPx
+    fun scaleToOffset(minPx: Float, maxPx: Float, userValue: Float) =
+        scale(valueRange.start, valueRange.endInclusive, userValue, minPx, maxPx)
+
+    var obtainedMeasurements = remember { mutableStateOf(false) }
+    val rawOffsetStart = remember { mutableStateOf(0f) }
+    val rawOffsetEnd = remember { mutableStateOf(0f) }
+
+    val gestureEndAction = rememberUpdatedState<(Boolean) -> Unit> {
+        onValueChangeFinished?.invoke()
+    }
+
+    val onDrag = rememberUpdatedState<(Boolean, Float) -> Unit> { isStart, offset ->
+        val maxPx = max(totalWidth - endThumbWidth / 2, 0f)
+        val minPx = min(startThumbWidth / 2, maxPx)
+        val offsetRange = if (isStart) {
+            rawOffsetStart.value = (rawOffsetStart.value + offset)
+            rawOffsetEnd.value = scaleToOffset(minPx, maxPx, value.endInclusive)
+            val offsetEnd = rawOffsetEnd.value
+            var offsetStart = rawOffsetStart.value.coerceIn(minPx, offsetEnd)
+            offsetStart = snapValueToTick(offsetStart, tickFractions, minPx, maxPx)
+            offsetStart..offsetEnd
+        } else {
+            rawOffsetEnd.value = (rawOffsetEnd.value + offset)
+            rawOffsetStart.value = scaleToOffset(minPx, maxPx, value.start)
+            val offsetStart = rawOffsetStart.value
+            var offsetEnd = rawOffsetEnd.value.coerceIn(offsetStart, maxPx)
+            offsetEnd = snapValueToTick(offsetEnd, tickFractions, minPx, maxPx)
+            offsetStart..offsetEnd
         }
 
-        val offsetStart = widthDp * positionFractionStart
-        val offsetEnd = widthDp * positionFractionEnd
+        onValueChangeState.value.invoke(scaleToUserValue(minPx, maxPx, offsetRange))
+    }
 
-        TempRangeSliderTrack(
-            Modifier
-                .align(Alignment.CenterStart)
-                .fillMaxSize(),
-            colors,
-            enabled,
-            positionFractionStart,
-            positionFractionEnd,
-            tickFractions,
-            ThumbWidth,
-            trackStrokeWidth
+    val pressDrag = Modifier.rangeSliderPressDragModifier(
+        startInteractionSource,
+        endInteractionSource,
+        rawOffsetStart,
+        rawOffsetEnd,
+        enabled,
+        isRtl,
+        totalWidth,
+        valueRange,
+        gestureEndAction,
+        onDrag,
+    )
+
+    // The positions of the thumbs are dependant on each other.
+    val coercedStart = value.start.coerceIn(valueRange.start, value.endInclusive)
+    val coercedEnd = value.endInclusive.coerceIn(value.start, valueRange.endInclusive)
+    val positionFractionStart = calcFraction(
+        valueRange.start,
+        valueRange.endInclusive,
+        coercedStart
+    )
+    val positionFractionEnd = calcFraction(valueRange.start, valueRange.endInclusive, coercedEnd)
+
+    val sliderPositions = remember {
+        SliderPositions(
+            positionFractionStart..positionFractionEnd,
+            tickFractions
+        )
+    }
+    sliderPositions.activeRange = positionFractionStart..positionFractionEnd
+    sliderPositions.tickFractions = tickFractions
+
+    val startSteps = floor(steps * positionFractionEnd).toInt()
+    val endSteps = floor(steps * (1f - positionFractionStart)).toInt()
+
+    val startThumbSemantics = Modifier.sliderSemantics(
+        coercedStart,
+        enabled,
+        { changedVal -> onValueChangeState.value.invoke(changedVal..coercedEnd) },
+        onValueChangeFinished,
+        valueRange.start..coercedEnd,
+        startSteps
+    )
+    val endThumbSemantics = Modifier.sliderSemantics(
+        coercedEnd,
+        enabled,
+        { changedVal -> onValueChangeState.value.invoke(coercedStart..changedVal) },
+        onValueChangeFinished,
+        coercedStart..valueRange.endInclusive,
+        endSteps
+    )
+
+    val startContentDescription = getString(Strings.SliderRangeStart)
+    val endContentDescription = getString(Strings.SliderRangeEnd)
+
+    Layout(
+        {
+            Box(modifier = Modifier
+                .layoutId(RangeSliderComponents.STARTTHUMB)
+                .semantics(mergeDescendants = true) {
+                    contentDescription = startContentDescription
+                }
+                .focusable(enabled, startInteractionSource)
+                .then(startThumbSemantics)
+            ) { startThumb(sliderPositions) }
+            Box(modifier = Modifier
+                .layoutId(RangeSliderComponents.ENDTHUMB)
+                .semantics(mergeDescendants = true) {
+                    contentDescription = endContentDescription
+                }
+                .focusable(enabled, endInteractionSource)
+                .then(endThumbSemantics)
+            ) { endThumb(sliderPositions) }
+            Box(modifier = Modifier.layoutId(RangeSliderComponents.TRACK)) {
+                track(sliderPositions)
+            }
+        },
+        modifier = modifier
+            .minimumInteractiveComponentSize()
+            .requiredSizeIn(
+                minWidth = SliderTokens.HandleWidth,
+                minHeight = SliderTokens.HandleHeight
+            )
+            .then(pressDrag)
+    ) { measurables, constraints ->
+        val startThumbPlaceable = measurables.first {
+            it.layoutId == RangeSliderComponents.STARTTHUMB
+        }.measure(
+            constraints
         )
 
-        TempRangeSliderThumb(
-            offset = offsetStart,
-            content = {
-                SliderDefaults.Thumb(
-                    modifier = Modifier
-                        .semantics(mergeDescendants = true) {
-                            contentDescription = startContentDescription
-                        }
-                        .focusable(true, startInteractionSource)
-                        .then(startThumbSemantics),
-                    colors = colors,
-                    enabled = enabled,
-                    interactionSource = startInteractionSource
-                )
-            }
+        val endThumbPlaceable = measurables.first {
+            it.layoutId == RangeSliderComponents.ENDTHUMB
+        }.measure(
+            constraints
         )
-        TempRangeSliderThumb(
-            offset = offsetEnd,
-            content = {
-                SliderDefaults.Thumb(
-                    modifier = Modifier
-                        .semantics(mergeDescendants = true) {
-                            contentDescription = endContentDescription
-                        }
-                        .focusable(true, endInteractionSource)
-                        .then(endThumbSemantics),
-                    colors = colors,
-                    enabled = enabled,
-                    interactionSource = endInteractionSource
-                )
-            }
+
+        val maxTrackWidth =
+            constraints.maxWidth - (startThumbPlaceable.width + endThumbPlaceable.width) / 2
+        val trackPlaceable = measurables.first {
+            it.layoutId == RangeSliderComponents.TRACK
+        }.measure(
+            constraints.copy(
+                minWidth = 0,
+                maxWidth = maxTrackWidth,
+                minHeight = 0
+            )
         )
+
+        val sliderWidth = trackPlaceable.width +
+            (startThumbPlaceable.width + endThumbPlaceable.width) / 2
+        val sliderHeight = maxOf(
+            trackPlaceable.height,
+            startThumbPlaceable.height,
+            endThumbPlaceable.height
+        )
+
+        startThumbWidth = startThumbPlaceable.width.toFloat()
+        endThumbWidth = endThumbPlaceable.width.toFloat()
+        totalWidth = sliderWidth
+
+        // Updates rawOffsetStart and rawOffsetEnd with the correct min and max pixel.
+        // We use this `obtainedMeasurements` boolean so that we only do this update once.
+        // Is there a cleaner way to do this?
+        if (!obtainedMeasurements.value) {
+            val finalizedMaxPx = max(totalWidth - endThumbWidth / 2, 0f)
+            val finalizedMinPx = min(startThumbWidth / 2, finalizedMaxPx)
+            rawOffsetStart.value = scaleToOffset(
+                finalizedMinPx,
+                finalizedMaxPx,
+                value.start
+            )
+            rawOffsetEnd.value = scaleToOffset(
+                finalizedMinPx,
+                finalizedMaxPx,
+                value.endInclusive
+            )
+            obtainedMeasurements.value = true
+        }
+
+        val trackOffsetX = startThumbPlaceable.width / 2
+        val startThumbOffsetX = (trackPlaceable.width * positionFractionStart).roundToInt()
+        // When start thumb and end thumb have different widths,
+        // we need to add a correction for the centering of the slider.
+        val endCorrection = (startThumbWidth - endThumbWidth) / 2
+        val endThumbOffsetX =
+            (trackPlaceable.width * positionFractionEnd + endCorrection).roundToInt()
+        val trackOffsetY = (sliderHeight - trackPlaceable.height) / 2
+        val startThumbOffsetY = (sliderHeight - startThumbPlaceable.height) / 2
+        val endThumbOffsetY = (sliderHeight - endThumbPlaceable.height) / 2
+
+        layout(
+            sliderWidth,
+            sliderHeight
+        ) {
+            trackPlaceable.placeRelative(
+                trackOffsetX,
+                trackOffsetY
+            )
+            startThumbPlaceable.placeRelative(
+                startThumbOffsetX,
+                startThumbOffsetY
+            )
+            endThumbPlaceable.placeRelative(
+                endThumbOffsetX,
+                endThumbOffsetY
+            )
+        }
     }
 }
 
 /**
  * Object to hold defaults used by [Slider]
  */
+@Stable
 object SliderDefaults {
 
     /**
@@ -718,14 +977,14 @@
     /**
      * The Default track for [Slider] and [RangeSlider]
      *
+     * @param sliderPositions [SliderPositions] which is used to obtain the current active track
+     * and the tick positions if the slider is discrete.
      * @param modifier the [Modifier] to be applied to the track.
      * @param colors [SliderColors] that will be used to resolve the colors used for this track in
      * different states. See [SliderDefaults.colors].
      * @param enabled controls the enabled state of this slider. When `false`, this component will
      * not respond to user input, and it will appear visually disabled and disabled to
      * accessibility services.
-     * @param sliderPositions [SliderPositions] which is used to obtain the current active track
-     * and the tick positions if the slider is discrete.
      */
     @Composable
     @ExperimentalMaterial3Api
@@ -759,13 +1018,13 @@
             )
             val sliderValueEnd = Offset(
                 sliderStart.x +
-                    (sliderEnd.x - sliderStart.x) * sliderPositions.positionFraction,
+                    (sliderEnd.x - sliderStart.x) * sliderPositions.activeRange.endInclusive,
                 center.y
             )
 
             val sliderValueStart = Offset(
                 sliderStart.x +
-                    (sliderEnd.x - sliderStart.x) * 0f,
+                    (sliderEnd.x - sliderStart.x) * sliderPositions.activeRange.start,
                 center.y
             )
 
@@ -777,8 +1036,8 @@
                 StrokeCap.Round
             )
             sliderPositions.tickFractions.groupBy {
-                it > sliderPositions.positionFraction ||
-                    it < 0f
+                it > sliderPositions.activeRange.endInclusive ||
+                    it < sliderPositions.activeRange.start
             }.forEach { (outsideFraction, list) ->
                     drawPoints(
                         list.map {
@@ -794,235 +1053,6 @@
     }
 }
 
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-private fun SliderImpl(
-    modifier: Modifier,
-    enabled: Boolean,
-    interactionSource: MutableInteractionSource,
-    onValueChange: (Float) -> Unit,
-    onValueChangeFinished: (() -> Unit)?,
-    steps: Int,
-    value: Float,
-    valueRange: ClosedFloatingPointRange<Float>,
-    thumb: @Composable (SliderPositions) -> Unit,
-    track: @Composable (SliderPositions) -> Unit
-) {
-    val onValueChangeState = rememberUpdatedState<(Float) -> Unit> {
-        if (it != value) {
-            onValueChange(it)
-        }
-    }
-
-    val tickFractions = remember(steps) {
-        stepsToTickFractions(steps)
-    }
-
-    val thumbWidth = remember { mutableStateOf(ThumbWidth.value) }
-    val totalWidth = remember { mutableStateOf(0) }
-
-    fun scaleToUserValue(minPx: Float, maxPx: Float, offset: Float) =
-        scale(minPx, maxPx, offset, valueRange.start, valueRange.endInclusive)
-
-    fun scaleToOffset(minPx: Float, maxPx: Float, userValue: Float) =
-        scale(valueRange.start, valueRange.endInclusive, userValue, minPx, maxPx)
-
-    val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
-    val rawOffset = remember { mutableStateOf(scaleToOffset(0f, 0f, value)) }
-    val pressOffset = remember { mutableStateOf(0f) }
-    val coerced = value.coerceIn(valueRange.start, valueRange.endInclusive)
-
-    val positionFraction = calcFraction(valueRange.start, valueRange.endInclusive, coerced)
-    val sliderPositions = remember { SliderPositions(positionFraction, tickFractions) }
-    sliderPositions.positionFraction = positionFraction
-    sliderPositions.tickFractions = tickFractions
-
-    val draggableState = remember(valueRange) {
-        SliderDraggableState {
-            val maxPx = max(totalWidth.value - thumbWidth.value / 2, 0f)
-            val minPx = min(thumbWidth.value / 2, maxPx)
-            rawOffset.value = (rawOffset.value + it + pressOffset.value)
-            pressOffset.value = 0f
-            val offsetInTrack = snapValueToTick(rawOffset.value, tickFractions, minPx, maxPx)
-            onValueChangeState.value.invoke(scaleToUserValue(minPx, maxPx, offsetInTrack))
-        }
-    }
-
-    val gestureEndAction = rememberUpdatedState {
-        if (!draggableState.isDragging) {
-            // check isDragging in case the change is still in progress (touch -> drag case)
-            onValueChangeFinished?.invoke()
-        }
-    }
-
-    val press = Modifier.sliderTapModifier(
-        draggableState,
-        interactionSource,
-        totalWidth.value,
-        isRtl,
-        rawOffset,
-        gestureEndAction,
-        pressOffset,
-        enabled
-    )
-
-    val drag = Modifier.draggable(
-        orientation = Orientation.Horizontal,
-        reverseDirection = isRtl,
-        enabled = enabled,
-        interactionSource = interactionSource,
-        onDragStopped = { _ -> gestureEndAction.value.invoke() },
-        startDragImmediately = draggableState.isDragging,
-        state = draggableState
-    )
-
-    Layout(
-        {
-            Box(modifier = Modifier.layoutId(SliderComponents.THUMB)) { thumb(sliderPositions) }
-            Box(modifier = Modifier.layoutId(SliderComponents.TRACK)) { track(sliderPositions) }
-        },
-        modifier = modifier
-            .minimumInteractiveComponentSize()
-            .requiredSizeIn(
-                minWidth = SliderTokens.HandleWidth,
-                minHeight = SliderTokens.HandleHeight
-            )
-            .sliderSemantics(
-                value,
-                enabled,
-                onValueChange,
-                onValueChangeFinished,
-                valueRange,
-                steps
-            )
-            .focusable(enabled, interactionSource)
-            .then(press)
-            .then(drag)
-    ) { measurables, constraints ->
-
-        val thumbPlaceable = measurables.first {
-            it.layoutId == SliderComponents.THUMB
-        }.measure(constraints)
-
-        val maxTrackWidth = constraints.maxWidth - thumbPlaceable.width
-        val trackPlaceable = measurables.first {
-            it.layoutId == SliderComponents.TRACK
-        }.measure(
-            constraints.copy(
-                minWidth = 0,
-                maxWidth = maxTrackWidth,
-                minHeight = 0
-            )
-        )
-
-        val sliderWidth = thumbPlaceable.width + trackPlaceable.width
-        val sliderHeight = max(trackPlaceable.height, thumbPlaceable.height)
-
-        thumbWidth.value = thumbPlaceable.width.toFloat()
-        totalWidth.value = sliderWidth
-
-        val trackOffsetX = thumbPlaceable.width / 2
-        val thumbOffsetX = ((trackPlaceable.width) * positionFraction).roundToInt()
-        val trackOffsetY = (sliderHeight - trackPlaceable.height) / 2
-        val thumbOffsetY = (sliderHeight - thumbPlaceable.height) / 2
-
-        layout(
-            sliderWidth,
-            sliderHeight
-        ) {
-            trackPlaceable.placeRelative(
-                trackOffsetX,
-                trackOffsetY
-            )
-            thumbPlaceable.placeRelative(
-                thumbOffsetX,
-                thumbOffsetY
-            )
-        }
-    }
-}
-
-// TODO: Remove during b/242600007
-@Composable
-private fun BoxScope.TempRangeSliderThumb(
-    offset: Dp,
-    content: @Composable BoxScope.() -> Unit
-) {
-    Box(
-        Modifier
-            .padding(start = offset)
-            .align(Alignment.CenterStart),
-        content = content
-    )
-}
-
-// TODO: Remove during b/242600007
-@Composable
-private fun TempRangeSliderTrack(
-    modifier: Modifier,
-    colors: SliderColors,
-    enabled: Boolean,
-    positionFractionStart: Float,
-    positionFractionEnd: Float,
-    tickFractions: FloatArray,
-    thumbWidth: Dp,
-    trackStrokeWidth: Float
-) {
-    val thumbRadiusPx: Float
-    val tickSize: Float
-    with(LocalDensity.current) {
-        thumbRadiusPx = thumbWidth.toPx() / 2
-        tickSize = TickSize.toPx()
-    }
-    val inactiveTrackColor = colors.trackColor(enabled, active = false)
-    val activeTrackColor = colors.trackColor(enabled, active = true)
-    val inactiveTickColor = colors.tickColor(enabled, active = false)
-    val activeTickColor = colors.tickColor(enabled, active = true)
-    Canvas(modifier) {
-        val isRtl = layoutDirection == LayoutDirection.Rtl
-        val sliderLeft = Offset(thumbRadiusPx, center.y)
-        val sliderRight = Offset(size.width - thumbRadiusPx, center.y)
-        val sliderStart = if (isRtl) sliderRight else sliderLeft
-        val sliderEnd = if (isRtl) sliderLeft else sliderRight
-        drawLine(
-            inactiveTrackColor.value,
-            sliderStart,
-            sliderEnd,
-            trackStrokeWidth,
-            StrokeCap.Round
-        )
-        val sliderValueEnd = Offset(
-            sliderStart.x + (sliderEnd.x - sliderStart.x) * positionFractionEnd,
-            center.y
-        )
-
-        val sliderValueStart = Offset(
-            sliderStart.x + (sliderEnd.x - sliderStart.x) * positionFractionStart,
-            center.y
-        )
-
-        drawLine(
-            activeTrackColor.value,
-            sliderValueStart,
-            sliderValueEnd,
-            trackStrokeWidth,
-            StrokeCap.Round
-        )
-        tickFractions.groupBy { it > positionFractionEnd || it < positionFractionStart }
-            .forEach { (outsideFraction, list) ->
-                drawPoints(
-                    list.map {
-                        Offset(lerp(sliderStart, sliderEnd, it).x, center.y)
-                    },
-                    PointMode.Points,
-                    (if (outsideFraction) inactiveTickColor else activeTickColor).value,
-                    tickSize,
-                    StrokeCap.Round
-                )
-            }
-    }
-}
-
 private fun snapValueToTick(
     current: Float,
     tickFractions: FloatArray,
@@ -1185,7 +1215,7 @@
     rawOffsetEnd: State<Float>,
     enabled: Boolean,
     isRtl: Boolean,
-    maxPx: Float,
+    maxPx: Int,
     valueRange: ClosedFloatingPointRange<Float>,
     gestureEndAction: State<(Boolean) -> Unit>,
     onDrag: State<(Boolean, Float) -> Unit>,
@@ -1413,21 +1443,27 @@
     TRACK
 }
 
+private enum class RangeSliderComponents {
+    ENDTHUMB,
+    STARTTHUMB,
+    TRACK
+}
+
 /**
- * Class that holds information about [Slider]'s active track and fractional
- * positions where the discrete ticks should be drawn on the track.
+ * Class that holds information about [Slider]'s and [RangeSlider]'s active track
+ * and fractional positions where the discrete ticks should be drawn on the track.
  */
 @Stable
 @ExperimentalMaterial3Api
 class SliderPositions(
-    initialPositionFraction: Float,
-    initialTickFractions: FloatArray
+    initialActiveRange: ClosedFloatingPointRange<Float> = 0f..1f,
+    initialTickFractions: FloatArray = floatArrayOf()
 ) {
     /**
-     * [Float] within the range [0f, 1f] that indicates the current position of
-     * the thumb as a fraction of the entire track.
+     * [ClosedFloatingPointRange] that indicates the current active range for the
+     * start to thumb for a [Slider] and start thumb to end thumb for a [RangeSlider].
      */
-    var positionFraction: Float by mutableStateOf(initialPositionFraction)
+    var activeRange: ClosedFloatingPointRange<Float> by mutableStateOf(initialActiveRange)
         internal set
 
     /**
@@ -1442,14 +1478,14 @@
         if (this === other) return true
         if (other !is SliderPositions) return false
 
-        if (positionFraction != other.positionFraction) return false
+        if (activeRange != other.activeRange) return false
         if (!tickFractions.contentEquals(other.tickFractions)) return false
 
         return true
     }
 
     override fun hashCode(): Int {
-        var result = positionFraction.hashCode()
+        var result = activeRange.hashCode()
         result = 31 * result + tickFractions.contentHashCode()
         return result
     }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Snackbar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Snackbar.kt
index 6dca6b4..85baad3 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Snackbar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Snackbar.kt
@@ -223,7 +223,7 @@
                     content = {
                         Icon(
                             Icons.Filled.Close,
-                            contentDescription = getString(Strings.Dismiss),
+                            contentDescription = getString(Strings.SnackbarDismiss),
                         )
                     }
                 )
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Strings.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Strings.kt
index f65a152..b4a1da0 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Strings.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Strings.kt
@@ -33,8 +33,8 @@
         val Dialog = Strings(7)
         val MenuExpanded = Strings(8)
         val MenuCollapsed = Strings(9)
-        val Dismiss = Strings(10)
-        val Search = Strings(11)
+        val SnackbarDismiss = Strings(10)
+        val SearchBarSearch = Strings(11)
         val SuggestionsAvailable = Strings(12)
         val DatePickerTitle = Strings(13)
         val DatePickerHeadline = Strings(14)
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismiss.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismiss.kt
index c16b1f0..271d0cf 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismiss.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeToDismiss.kt
@@ -237,6 +237,7 @@
     directions: Set<DismissDirection> = setOf(EndToStart, StartToEnd),
 ) {
     val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
+
     Box(
         modifier
             .swipeableV2(
diff --git a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material/Strings.desktop.kt b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material/Strings.desktop.kt
index 776da50..e2d8ba4 100644
--- a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material/Strings.desktop.kt
+++ b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material/Strings.desktop.kt
@@ -30,8 +30,8 @@
         Strings.Dialog -> "Dialog"
         Strings.MenuExpanded -> "Expanded"
         Strings.MenuCollapsed -> "Collapsed"
-        Strings.Dismiss -> "Dismiss"
-        Strings.Search -> "Search"
+        Strings.SnackbarDismiss -> "Dismiss"
+        Strings.SearchBarSearch -> "Search"
         Strings.SuggestionsAvailable -> "Suggestions below"
         Strings.DatePickerTitle -> "Select date"
         Strings.DatePickerHeadline -> "Selected date"
diff --git a/compose/runtime/runtime-rxjava3/src/main/java/androidx/compose/runtime/rxjava3/RxJava3Adapter.kt b/compose/runtime/runtime-rxjava3/src/main/java/androidx/compose/runtime/rxjava3/RxJava3Adapter.kt
index 9994c9c..c4c31a5 100644
--- a/compose/runtime/runtime-rxjava3/src/main/java/androidx/compose/runtime/rxjava3/RxJava3Adapter.kt
+++ b/compose/runtime/runtime-rxjava3/src/main/java/androidx/compose/runtime/rxjava3/RxJava3Adapter.kt
@@ -45,7 +45,7 @@
  * @param initial The initial value for the returned [State] which will be asynchronously updated
  * with the real one once we receive it from the stream
  */
-@Suppress("UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS")
+@Suppress("UPPER_BOUND_VIOLATED")
 @Composable
 fun <R, T : R> Observable<T>.subscribeAsState(initial: R): State<R> =
     asState(initial) { subscribe(it) }
@@ -66,7 +66,7 @@
  * @param initial The initial value for the returned [State] which will be asynchronously updated
  * with the real one once we receive it from the stream
  */
-@Suppress("UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS")
+@Suppress("UPPER_BOUND_VIOLATED")
 @Composable
 fun <R, T : R> Flowable<T>.subscribeAsState(initial: R): State<R> =
     asState(initial) { subscribe(it) }
@@ -87,7 +87,7 @@
  * @param initial The initial value for the returned [State] which will be asynchronously updated
  * with the real one once we receive it from the stream
  */
-@Suppress("UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS")
+@Suppress("UPPER_BOUND_VIOLATED")
 @Composable
 fun <R, T : R> Single<T>.subscribeAsState(initial: R): State<R> =
     asState(initial) { subscribe(it) }
diff --git a/compose/runtime/runtime/api/current.ignore b/compose/runtime/runtime/api/current.ignore
index e6959b9..b2e1b75 100644
--- a/compose/runtime/runtime/api/current.ignore
+++ b/compose/runtime/runtime/api/current.ignore
@@ -5,3 +5,7 @@
     Added method androidx.compose.runtime.Composer.endToMarker(int)
 AddedAbstractMethod: androidx.compose.runtime.Composer#getCurrentMarker():
     Added method androidx.compose.runtime.Composer.getCurrentMarker()
+
+
+RemovedMethod: androidx.compose.runtime.collection.MutableVectorKt#mutableVectorOf(T):
+    Removed method androidx.compose.runtime.collection.MutableVectorKt.mutableVectorOf(T)
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index cd22d03..b885979 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -647,7 +647,7 @@
     method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T>! MutableVector(optional int capacity);
     method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T> MutableVector(int size, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends T> init);
     method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T>! mutableVectorOf();
-    method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T> mutableVectorOf(T? elements);
+    method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T> mutableVectorOf(T?... elements);
   }
 
 }
diff --git a/compose/runtime/runtime/api/public_plus_experimental_current.txt b/compose/runtime/runtime/api/public_plus_experimental_current.txt
index 9e24b69..969339d 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_current.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_current.txt
@@ -696,7 +696,7 @@
     method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T>! MutableVector(optional int capacity);
     method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T> MutableVector(int size, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends T> init);
     method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T>! mutableVectorOf();
-    method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T> mutableVectorOf(T? elements);
+    method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T> mutableVectorOf(T?... elements);
   }
 
 }
diff --git a/compose/runtime/runtime/api/restricted_current.ignore b/compose/runtime/runtime/api/restricted_current.ignore
index e6959b9..b2e1b75 100644
--- a/compose/runtime/runtime/api/restricted_current.ignore
+++ b/compose/runtime/runtime/api/restricted_current.ignore
@@ -5,3 +5,7 @@
     Added method androidx.compose.runtime.Composer.endToMarker(int)
 AddedAbstractMethod: androidx.compose.runtime.Composer#getCurrentMarker():
     Added method androidx.compose.runtime.Composer.getCurrentMarker()
+
+
+RemovedMethod: androidx.compose.runtime.collection.MutableVectorKt#mutableVectorOf(T):
+    Removed method androidx.compose.runtime.collection.MutableVectorKt.mutableVectorOf(T)
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index 40275f6..bd0e1a0 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -676,7 +676,7 @@
     method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T>! MutableVector(optional int capacity);
     method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T> MutableVector(int size, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends T> init);
     method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T>! mutableVectorOf();
-    method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T> mutableVectorOf(T? elements);
+    method public static inline <reified T> androidx.compose.runtime.collection.MutableVector<T> mutableVectorOf(T?... elements);
   }
 
 }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
index 992384a..be676a1 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeVersion.kt
@@ -28,5 +28,5 @@
      * IMPORTANT: Whenever updating this value, please make sure to also update `versionTable` and
      * `minimumRuntimeVersionInt` in `VersionChecker.kt` of the compiler.
      */
-    const val version: Int = 9200
+    const val version: Int = 9300
 }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
index 79d6d48..62189bc 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
@@ -32,7 +32,6 @@
 import kotlin.coroutines.EmptyCoroutineContext
 import kotlin.coroutines.coroutineContext
 import kotlin.coroutines.resume
-import kotlin.native.concurrent.ThreadLocal
 import kotlinx.coroutines.CancellableContinuation
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineScope
@@ -1243,7 +1242,8 @@
      *
      * This annotation WILL BE REMOVED with the new memory model of Kotlin/Native.
      */
-    @ThreadLocal
+    @Suppress("OPTIONAL_DECLARATION_USAGE_IN_NON_COMMON_SOURCE")
+    @kotlin.native.concurrent.ThreadLocal
     companion object {
 
         private val _runningRecomposers = MutableStateFlow(persistentSetOf<RecomposerInfoImpl>())
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapContentIterators.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapContentIterators.kt
index b10deaf..d5619fd 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapContentIterators.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableMap/PersistentHashMapContentIterators.kt
@@ -6,7 +6,6 @@
 package androidx.compose.runtime.external.kotlinx.collections.immutable.implementations.immutableMap
 
 import androidx.compose.runtime.external.kotlinx.collections.immutable.internal.assert
-import kotlin.js.JsName
 
 internal const val TRIE_MAX_HEIGHT = 7
 
@@ -104,7 +103,8 @@
 ) : Iterator<T> {
 
     protected var pathLastIndex = 0
-    @JsName("_hasNext")
+    @Suppress("OPTIONAL_DECLARATION_USAGE_IN_NON_COMMON_SOURCE")
+    @kotlin.js.JsName("_hasNext")
     private var hasNext = true
 
     init {
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableSet/PersistentHashSetIterator.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableSet/PersistentHashSetIterator.kt
index 8810019..b9738ef 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableSet/PersistentHashSetIterator.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/external/kotlinx/collections/immutable/implementations/immutableSet/PersistentHashSetIterator.kt
@@ -6,12 +6,12 @@
 package androidx.compose.runtime.external.kotlinx.collections.immutable.implementations.immutableSet
 
 import androidx.compose.runtime.external.kotlinx.collections.immutable.internal.assert
-import kotlin.js.JsName
 
 internal open class PersistentHashSetIterator<E>(node: TrieNode<E>) : Iterator<E> {
     protected val path = mutableListOf(TrieNodeIterator<E>())
     protected var pathLastIndex = 0
-    @JsName("_hasNext")
+    @Suppress("OPTIONAL_DECLARATION_USAGE_IN_NON_COMMON_SOURCE")
+    @kotlin.js.JsName("_hasNext")
     private var hasNext = true
 
     init {
diff --git a/compose/ui/ui-graphics/api/current.ignore b/compose/ui/ui-graphics/api/current.ignore
new file mode 100644
index 0000000..ac8aa37
--- /dev/null
+++ b/compose/ui/ui-graphics/api/current.ignore
@@ -0,0 +1,5 @@
+// Baseline format: 1.0
+AddedAbstractMethod: androidx.compose.ui.graphics.PathMeasure#getPosition(float):
+    Added method androidx.compose.ui.graphics.PathMeasure.getPosition(float)
+AddedAbstractMethod: androidx.compose.ui.graphics.PathMeasure#getTangent(float):
+    Added method androidx.compose.ui.graphics.PathMeasure.getTangent(float)
diff --git a/compose/ui/ui-graphics/api/current.txt b/compose/ui/ui-graphics/api/current.txt
index f1c4192..8f387eca 100644
--- a/compose/ui/ui-graphics/api/current.txt
+++ b/compose/ui/ui-graphics/api/current.txt
@@ -115,7 +115,9 @@
 
   public final class AndroidPathMeasure implements androidx.compose.ui.graphics.PathMeasure {
     method public float getLength();
+    method public long getPosition(float distance);
     method public boolean getSegment(float startDistance, float stopDistance, androidx.compose.ui.graphics.Path destination, boolean startWithMoveTo);
+    method public long getTangent(float distance);
     method public void setPath(androidx.compose.ui.graphics.Path? path, boolean forceClosed);
     property public float length;
   }
@@ -666,7 +668,9 @@
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface PathMeasure {
     method public float getLength();
+    method public long getPosition(float distance);
     method public boolean getSegment(float startDistance, float stopDistance, androidx.compose.ui.graphics.Path destination, optional boolean startWithMoveTo);
+    method public long getTangent(float distance);
     method public void setPath(androidx.compose.ui.graphics.Path? path, boolean forceClosed);
     property public abstract float length;
   }
diff --git a/compose/ui/ui-graphics/api/public_plus_experimental_current.txt b/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
index 90bb660..f5a56d4 100644
--- a/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-graphics/api/public_plus_experimental_current.txt
@@ -115,7 +115,9 @@
 
   public final class AndroidPathMeasure implements androidx.compose.ui.graphics.PathMeasure {
     method public float getLength();
+    method public long getPosition(float distance);
     method public boolean getSegment(float startDistance, float stopDistance, androidx.compose.ui.graphics.Path destination, boolean startWithMoveTo);
+    method public long getTangent(float distance);
     method public void setPath(androidx.compose.ui.graphics.Path? path, boolean forceClosed);
     property public float length;
   }
@@ -669,7 +671,9 @@
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface PathMeasure {
     method public float getLength();
+    method public long getPosition(float distance);
     method public boolean getSegment(float startDistance, float stopDistance, androidx.compose.ui.graphics.Path destination, optional boolean startWithMoveTo);
+    method public long getTangent(float distance);
     method public void setPath(androidx.compose.ui.graphics.Path? path, boolean forceClosed);
     property public abstract float length;
   }
diff --git a/compose/ui/ui-graphics/api/restricted_current.ignore b/compose/ui/ui-graphics/api/restricted_current.ignore
new file mode 100644
index 0000000..ac8aa37
--- /dev/null
+++ b/compose/ui/ui-graphics/api/restricted_current.ignore
@@ -0,0 +1,5 @@
+// Baseline format: 1.0
+AddedAbstractMethod: androidx.compose.ui.graphics.PathMeasure#getPosition(float):
+    Added method androidx.compose.ui.graphics.PathMeasure.getPosition(float)
+AddedAbstractMethod: androidx.compose.ui.graphics.PathMeasure#getTangent(float):
+    Added method androidx.compose.ui.graphics.PathMeasure.getTangent(float)
diff --git a/compose/ui/ui-graphics/api/restricted_current.txt b/compose/ui/ui-graphics/api/restricted_current.txt
index adad1a5..a7f88e5 100644
--- a/compose/ui/ui-graphics/api/restricted_current.txt
+++ b/compose/ui/ui-graphics/api/restricted_current.txt
@@ -145,7 +145,9 @@
 
   public final class AndroidPathMeasure implements androidx.compose.ui.graphics.PathMeasure {
     method public float getLength();
+    method public long getPosition(float distance);
     method public boolean getSegment(float startDistance, float stopDistance, androidx.compose.ui.graphics.Path destination, boolean startWithMoveTo);
+    method public long getTangent(float distance);
     method public void setPath(androidx.compose.ui.graphics.Path? path, boolean forceClosed);
     property public float length;
   }
@@ -698,7 +700,9 @@
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface PathMeasure {
     method public float getLength();
+    method public long getPosition(float distance);
     method public boolean getSegment(float startDistance, float stopDistance, androidx.compose.ui.graphics.Path destination, optional boolean startWithMoveTo);
+    method public long getTangent(float distance);
     method public void setPath(androidx.compose.ui.graphics.Path? path, boolean forceClosed);
     property public abstract float length;
   }
diff --git a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/PathMeasureTest.kt b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/PathMeasureTest.kt
new file mode 100644
index 0000000..afacfe3
--- /dev/null
+++ b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/PathMeasureTest.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.compose.ui.graphics
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PathMeasureTest {
+
+    @Test
+    fun testGetPositionAndTangent() {
+        val width = 100f
+        val height = 100f
+        val path = Path().apply {
+            lineTo(width, height)
+        }
+        val pathMeasure = PathMeasure()
+
+        pathMeasure.setPath(path, false)
+        val distance = pathMeasure.length
+        val position = pathMeasure.getPosition(distance * 0.5f)
+
+        val tangent = pathMeasure.getTangent(distance * 0.5f)
+
+        Assert.assertEquals(50f, position.x)
+        Assert.assertEquals(50f, position.y)
+        Assert.assertEquals(0.707106f, tangent.x, 0.00001f)
+        Assert.assertEquals(0.707106f, tangent.y, 0.00001f)
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidPathMeasure.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidPathMeasure.android.kt
index d9996fa..358b3af 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidPathMeasure.android.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidPathMeasure.android.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.ui.graphics
 
+import androidx.compose.ui.geometry.Offset
+
 actual fun PathMeasure(): PathMeasure = AndroidPathMeasure(android.graphics.PathMeasure())
 
 class AndroidPathMeasure internal constructor(
@@ -25,6 +27,10 @@
     override val length: Float
         get() = internalPathMeasure.length
 
+    private var positionArray: FloatArray? = null
+
+    private var tangentArray: FloatArray? = null
+
     override fun getSegment(
         startDistance: Float,
         stopDistance: Float,
@@ -42,4 +48,38 @@
     override fun setPath(path: Path?, forceClosed: Boolean) {
         internalPathMeasure.setPath(path?.asAndroidPath(), forceClosed)
     }
+
+    override fun getPosition(
+        distance: Float
+    ): Offset {
+        if (positionArray == null) {
+            positionArray = FloatArray(2)
+        }
+        if (tangentArray == null) {
+            tangentArray = FloatArray(2)
+        }
+        val result = internalPathMeasure.getPosTan(distance, positionArray, tangentArray)
+        return if (result) {
+            Offset(positionArray!![0], positionArray!![1])
+        } else {
+            Offset.Unspecified
+        }
+    }
+
+    override fun getTangent(
+        distance: Float
+    ): Offset {
+        if (positionArray == null) {
+            positionArray = FloatArray(2)
+        }
+        if (tangentArray == null) {
+            tangentArray = FloatArray(2)
+        }
+        val result = internalPathMeasure.getPosTan(distance, positionArray, tangentArray)
+        return if (result) {
+            Offset(tangentArray!![0], tangentArray!![1])
+        } else {
+            Offset.Unspecified
+        }
+    }
 }
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathMeasure.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathMeasure.kt
index 9e168cb..d2ee71f 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathMeasure.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/PathMeasure.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.graphics
 
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.internal.JvmDefaultWithCompatibility
 
 /**
@@ -53,4 +54,22 @@
      * Assign a new path, or null to have none.
      */
     fun setPath(path: Path?, forceClosed: Boolean)
+
+    /**
+     * Pins distance to 0 <= distance <= getLength(), and then computes the corresponding position
+     *
+     * @param distance The distance along the current contour to sample
+     *
+     * @return [Offset.Unspecified] if there is no path set
+     */
+    fun getPosition(distance: Float): Offset
+
+    /**
+     * Pins distance to 0 <= distance <= getLength(), and then computes the corresponding tangent
+     *
+     * @param distance The distance along the current contour to sample
+     *
+     * @return [Offset.Unspecified] if there is no path set
+     */
+    fun getTangent(distance: Float): Offset
 }
diff --git a/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedPathMeasure.skiko.kt b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedPathMeasure.skiko.kt
index 7658c68..810a409 100644
--- a/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedPathMeasure.skiko.kt
+++ b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedPathMeasure.skiko.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.graphics
 
+import androidx.compose.ui.geometry.Offset
 /**
  * Convert the [org.jetbrains.skia.PathMeasure] instance into a Compose-compatible PathMeasure
  */
@@ -49,6 +50,28 @@
 
     override val length: Float
         get() = skia.length
+
+    override fun getPosition(
+        distance: Float
+    ): Offset {
+        val result = skia.getPosition(distance)
+        return if (result != null) {
+            Offset(result.x, result.y)
+        } else {
+            Offset.Unspecified
+        }
+    }
+
+    override fun getTangent(
+        distance: Float
+    ): Offset {
+        val result = skia.getTangent(distance)
+        return if (result != null) {
+            Offset(result.x, result.y)
+        } else {
+            Offset.Unspecified
+        }
+    }
 }
 
 actual fun PathMeasure(): PathMeasure =
diff --git a/compose/ui/ui-test-junit4/OWNERS b/compose/ui/ui-test-junit4/OWNERS
index 90ad461..0b4fcdd 100644
--- a/compose/ui/ui-test-junit4/OWNERS
+++ b/compose/ui/ui-test-junit4/OWNERS
@@ -1,2 +1,3 @@
 # Bug component: 741702
 jellefresen@google.com
+klippenstein@google.com
diff --git a/compose/ui/ui-test/OWNERS b/compose/ui/ui-test/OWNERS
index 90ad461..0b4fcdd 100644
--- a/compose/ui/ui-test/OWNERS
+++ b/compose/ui/ui-test/OWNERS
@@ -1,2 +1,3 @@
 # Bug component: 741702
 jellefresen@google.com
+klippenstein@google.com
diff --git a/compose/ui/ui-text/api/current.ignore b/compose/ui/ui-text/api/current.ignore
index a4b016c..24501fb 100644
--- a/compose/ui/ui-text/api/current.ignore
+++ b/compose/ui/ui-text/api/current.ignore
@@ -1,13 +1,3 @@
 // Baseline format: 1.0
 ChangedType: androidx.compose.ui.text.AnnotatedString.Builder#append(char):
     Method androidx.compose.ui.text.AnnotatedString.Builder.append has changed return type from void to androidx.compose.ui.text.AnnotatedString.Builder
-
-
-InvalidNullConversion: androidx.compose.ui.text.TextStyle#TextStyle(long, long, androidx.compose.ui.text.font.FontWeight, androidx.compose.ui.text.font.FontStyle, androidx.compose.ui.text.font.FontSynthesis, androidx.compose.ui.text.font.FontFamily, String, long, androidx.compose.ui.text.style.BaselineShift, androidx.compose.ui.text.style.TextGeometricTransform, androidx.compose.ui.text.intl.LocaleList, long, androidx.compose.ui.text.style.TextDecoration, androidx.compose.ui.graphics.Shadow, androidx.compose.ui.text.style.TextAlign, androidx.compose.ui.text.style.TextDirection, long, androidx.compose.ui.text.style.TextIndent) parameter #6:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter fontFeatureSettings in androidx.compose.ui.text.TextStyle(long color, long fontSize, androidx.compose.ui.text.font.FontWeight fontWeight, androidx.compose.ui.text.font.FontStyle fontStyle, androidx.compose.ui.text.font.FontSynthesis fontSynthesis, androidx.compose.ui.text.font.FontFamily fontFamily, String fontFeatureSettings, long letterSpacing, androidx.compose.ui.text.style.BaselineShift baselineShift, androidx.compose.ui.text.style.TextGeometricTransform textGeometricTransform, androidx.compose.ui.text.intl.LocaleList localeList, long background, androidx.compose.ui.text.style.TextDecoration textDecoration, androidx.compose.ui.graphics.Shadow shadow, androidx.compose.ui.text.style.TextAlign textAlign, androidx.compose.ui.text.style.TextDirection textDirection, long lineHeight, androidx.compose.ui.text.style.TextIndent textIndent)
-InvalidNullConversion: androidx.compose.ui.text.TextStyle#TextStyle(long, long, androidx.compose.ui.text.font.FontWeight, androidx.compose.ui.text.font.FontStyle, androidx.compose.ui.text.font.FontSynthesis, androidx.compose.ui.text.font.FontFamily, String, long, androidx.compose.ui.text.style.BaselineShift, androidx.compose.ui.text.style.TextGeometricTransform, androidx.compose.ui.text.intl.LocaleList, long, androidx.compose.ui.text.style.TextDecoration, androidx.compose.ui.graphics.Shadow, androidx.compose.ui.text.style.TextAlign, androidx.compose.ui.text.style.TextDirection, long, androidx.compose.ui.text.style.TextIndent, androidx.compose.ui.text.PlatformTextStyle, androidx.compose.ui.text.style.LineHeightStyle) parameter #6:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter fontFeatureSettings in androidx.compose.ui.text.TextStyle(long color, long fontSize, androidx.compose.ui.text.font.FontWeight fontWeight, androidx.compose.ui.text.font.FontStyle fontStyle, androidx.compose.ui.text.font.FontSynthesis fontSynthesis, androidx.compose.ui.text.font.FontFamily fontFamily, String fontFeatureSettings, long letterSpacing, androidx.compose.ui.text.style.BaselineShift baselineShift, androidx.compose.ui.text.style.TextGeometricTransform textGeometricTransform, androidx.compose.ui.text.intl.LocaleList localeList, long background, androidx.compose.ui.text.style.TextDecoration textDecoration, androidx.compose.ui.graphics.Shadow shadow, androidx.compose.ui.text.style.TextAlign textAlign, androidx.compose.ui.text.style.TextDirection textDirection, long lineHeight, androidx.compose.ui.text.style.TextIndent textIndent, androidx.compose.ui.text.PlatformTextStyle platformStyle, androidx.compose.ui.text.style.LineHeightStyle lineHeightStyle)
-InvalidNullConversion: androidx.compose.ui.text.TextStyle#copy(long, long, androidx.compose.ui.text.font.FontWeight, androidx.compose.ui.text.font.FontStyle, androidx.compose.ui.text.font.FontSynthesis, androidx.compose.ui.text.font.FontFamily, String, long, androidx.compose.ui.text.style.BaselineShift, androidx.compose.ui.text.style.TextGeometricTransform, androidx.compose.ui.text.intl.LocaleList, long, androidx.compose.ui.text.style.TextDecoration, androidx.compose.ui.graphics.Shadow, androidx.compose.ui.text.style.TextAlign, androidx.compose.ui.text.style.TextDirection, long, androidx.compose.ui.text.style.TextIndent) parameter #6:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter fontFeatureSettings in androidx.compose.ui.text.TextStyle.copy(long color, long fontSize, androidx.compose.ui.text.font.FontWeight fontWeight, androidx.compose.ui.text.font.FontStyle fontStyle, androidx.compose.ui.text.font.FontSynthesis fontSynthesis, androidx.compose.ui.text.font.FontFamily fontFamily, String fontFeatureSettings, long letterSpacing, androidx.compose.ui.text.style.BaselineShift baselineShift, androidx.compose.ui.text.style.TextGeometricTransform textGeometricTransform, androidx.compose.ui.text.intl.LocaleList localeList, long background, androidx.compose.ui.text.style.TextDecoration textDecoration, androidx.compose.ui.graphics.Shadow shadow, androidx.compose.ui.text.style.TextAlign textAlign, androidx.compose.ui.text.style.TextDirection textDirection, long lineHeight, androidx.compose.ui.text.style.TextIndent textIndent)
-InvalidNullConversion: androidx.compose.ui.text.TextStyle#copy(long, long, androidx.compose.ui.text.font.FontWeight, androidx.compose.ui.text.font.FontStyle, androidx.compose.ui.text.font.FontSynthesis, androidx.compose.ui.text.font.FontFamily, String, long, androidx.compose.ui.text.style.BaselineShift, androidx.compose.ui.text.style.TextGeometricTransform, androidx.compose.ui.text.intl.LocaleList, long, androidx.compose.ui.text.style.TextDecoration, androidx.compose.ui.graphics.Shadow, androidx.compose.ui.text.style.TextAlign, androidx.compose.ui.text.style.TextDirection, long, androidx.compose.ui.text.style.TextIndent, androidx.compose.ui.text.PlatformTextStyle, androidx.compose.ui.text.style.LineHeightStyle) parameter #6:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter fontFeatureSettings in androidx.compose.ui.text.TextStyle.copy(long color, long fontSize, androidx.compose.ui.text.font.FontWeight fontWeight, androidx.compose.ui.text.font.FontStyle fontStyle, androidx.compose.ui.text.font.FontSynthesis fontSynthesis, androidx.compose.ui.text.font.FontFamily fontFamily, String fontFeatureSettings, long letterSpacing, androidx.compose.ui.text.style.BaselineShift baselineShift, androidx.compose.ui.text.style.TextGeometricTransform textGeometricTransform, androidx.compose.ui.text.intl.LocaleList localeList, long background, androidx.compose.ui.text.style.TextDecoration textDecoration, androidx.compose.ui.graphics.Shadow shadow, androidx.compose.ui.text.style.TextAlign textAlign, androidx.compose.ui.text.style.TextDirection textDirection, long lineHeight, androidx.compose.ui.text.style.TextIndent textIndent, androidx.compose.ui.text.PlatformTextStyle platformStyle, androidx.compose.ui.text.style.LineHeightStyle lineHeightStyle)
diff --git a/compose/ui/ui-text/api/current.txt b/compose/ui/ui-text/api/current.txt
index bb94921..df13e31 100644
--- a/compose/ui/ui-text/api/current.txt
+++ b/compose/ui/ui-text/api/current.txt
@@ -22,6 +22,7 @@
     method public java.util.List<androidx.compose.ui.text.AnnotatedString.Range<java.lang.String>> getStringAnnotations(int start, int end);
     method public String getText();
     method public java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.TtsAnnotation>> getTtsAnnotations(int start, int end);
+    method public boolean hasStringAnnotations(String tag, int start, int end);
     method @androidx.compose.runtime.Stable public operator androidx.compose.ui.text.AnnotatedString plus(androidx.compose.ui.text.AnnotatedString other);
     method public androidx.compose.ui.text.AnnotatedString subSequence(int startIndex, int endIndex);
     method public androidx.compose.ui.text.AnnotatedString subSequence(long range);
@@ -513,11 +514,11 @@
 
   @androidx.compose.runtime.Immutable public final class TextStyle {
     ctor public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens);
-    ctor @Deprecated public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent);
-    ctor @Deprecated public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle);
+    ctor @Deprecated public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent);
+    ctor @Deprecated public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle);
     method public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens);
-    method @Deprecated public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent);
-    method @Deprecated public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle);
+    method @Deprecated public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent);
+    method @Deprecated public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle);
     method public long getBackground();
     method public androidx.compose.ui.text.style.BaselineShift? getBaselineShift();
     method public long getColor();
diff --git a/compose/ui/ui-text/api/public_plus_experimental_current.txt b/compose/ui/ui-text/api/public_plus_experimental_current.txt
index 553efb3..b27caf4 100644
--- a/compose/ui/ui-text/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-text/api/public_plus_experimental_current.txt
@@ -23,6 +23,7 @@
     method public String getText();
     method public java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.TtsAnnotation>> getTtsAnnotations(int start, int end);
     method @androidx.compose.ui.text.ExperimentalTextApi public java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.UrlAnnotation>> getUrlAnnotations(int start, int end);
+    method public boolean hasStringAnnotations(String tag, int start, int end);
     method @androidx.compose.runtime.Stable public operator androidx.compose.ui.text.AnnotatedString plus(androidx.compose.ui.text.AnnotatedString other);
     method public androidx.compose.ui.text.AnnotatedString subSequence(int startIndex, int endIndex);
     method public androidx.compose.ui.text.AnnotatedString subSequence(long range);
@@ -556,13 +557,13 @@
     ctor public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens);
     ctor @androidx.compose.ui.text.ExperimentalTextApi public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
     ctor @androidx.compose.ui.text.ExperimentalTextApi public TextStyle(androidx.compose.ui.graphics.Brush? brush, optional float alpha, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
-    ctor @Deprecated public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent);
-    ctor @Deprecated public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle);
+    ctor @Deprecated public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent);
+    ctor @Deprecated public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle);
     method public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens);
     method @androidx.compose.ui.text.ExperimentalTextApi public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
     method @androidx.compose.ui.text.ExperimentalTextApi public androidx.compose.ui.text.TextStyle copy(androidx.compose.ui.graphics.Brush? brush, optional float alpha, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens, optional androidx.compose.ui.text.style.TextMotion? textMotion);
-    method @Deprecated public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent);
-    method @Deprecated public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle);
+    method @Deprecated public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent);
+    method @Deprecated public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle);
     method @androidx.compose.ui.text.ExperimentalTextApi public float getAlpha();
     method public long getBackground();
     method public androidx.compose.ui.text.style.BaselineShift? getBaselineShift();
diff --git a/compose/ui/ui-text/api/restricted_current.ignore b/compose/ui/ui-text/api/restricted_current.ignore
index a4b016c..24501fb 100644
--- a/compose/ui/ui-text/api/restricted_current.ignore
+++ b/compose/ui/ui-text/api/restricted_current.ignore
@@ -1,13 +1,3 @@
 // Baseline format: 1.0
 ChangedType: androidx.compose.ui.text.AnnotatedString.Builder#append(char):
     Method androidx.compose.ui.text.AnnotatedString.Builder.append has changed return type from void to androidx.compose.ui.text.AnnotatedString.Builder
-
-
-InvalidNullConversion: androidx.compose.ui.text.TextStyle#TextStyle(long, long, androidx.compose.ui.text.font.FontWeight, androidx.compose.ui.text.font.FontStyle, androidx.compose.ui.text.font.FontSynthesis, androidx.compose.ui.text.font.FontFamily, String, long, androidx.compose.ui.text.style.BaselineShift, androidx.compose.ui.text.style.TextGeometricTransform, androidx.compose.ui.text.intl.LocaleList, long, androidx.compose.ui.text.style.TextDecoration, androidx.compose.ui.graphics.Shadow, androidx.compose.ui.text.style.TextAlign, androidx.compose.ui.text.style.TextDirection, long, androidx.compose.ui.text.style.TextIndent) parameter #6:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter fontFeatureSettings in androidx.compose.ui.text.TextStyle(long color, long fontSize, androidx.compose.ui.text.font.FontWeight fontWeight, androidx.compose.ui.text.font.FontStyle fontStyle, androidx.compose.ui.text.font.FontSynthesis fontSynthesis, androidx.compose.ui.text.font.FontFamily fontFamily, String fontFeatureSettings, long letterSpacing, androidx.compose.ui.text.style.BaselineShift baselineShift, androidx.compose.ui.text.style.TextGeometricTransform textGeometricTransform, androidx.compose.ui.text.intl.LocaleList localeList, long background, androidx.compose.ui.text.style.TextDecoration textDecoration, androidx.compose.ui.graphics.Shadow shadow, androidx.compose.ui.text.style.TextAlign textAlign, androidx.compose.ui.text.style.TextDirection textDirection, long lineHeight, androidx.compose.ui.text.style.TextIndent textIndent)
-InvalidNullConversion: androidx.compose.ui.text.TextStyle#TextStyle(long, long, androidx.compose.ui.text.font.FontWeight, androidx.compose.ui.text.font.FontStyle, androidx.compose.ui.text.font.FontSynthesis, androidx.compose.ui.text.font.FontFamily, String, long, androidx.compose.ui.text.style.BaselineShift, androidx.compose.ui.text.style.TextGeometricTransform, androidx.compose.ui.text.intl.LocaleList, long, androidx.compose.ui.text.style.TextDecoration, androidx.compose.ui.graphics.Shadow, androidx.compose.ui.text.style.TextAlign, androidx.compose.ui.text.style.TextDirection, long, androidx.compose.ui.text.style.TextIndent, androidx.compose.ui.text.PlatformTextStyle, androidx.compose.ui.text.style.LineHeightStyle) parameter #6:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter fontFeatureSettings in androidx.compose.ui.text.TextStyle(long color, long fontSize, androidx.compose.ui.text.font.FontWeight fontWeight, androidx.compose.ui.text.font.FontStyle fontStyle, androidx.compose.ui.text.font.FontSynthesis fontSynthesis, androidx.compose.ui.text.font.FontFamily fontFamily, String fontFeatureSettings, long letterSpacing, androidx.compose.ui.text.style.BaselineShift baselineShift, androidx.compose.ui.text.style.TextGeometricTransform textGeometricTransform, androidx.compose.ui.text.intl.LocaleList localeList, long background, androidx.compose.ui.text.style.TextDecoration textDecoration, androidx.compose.ui.graphics.Shadow shadow, androidx.compose.ui.text.style.TextAlign textAlign, androidx.compose.ui.text.style.TextDirection textDirection, long lineHeight, androidx.compose.ui.text.style.TextIndent textIndent, androidx.compose.ui.text.PlatformTextStyle platformStyle, androidx.compose.ui.text.style.LineHeightStyle lineHeightStyle)
-InvalidNullConversion: androidx.compose.ui.text.TextStyle#copy(long, long, androidx.compose.ui.text.font.FontWeight, androidx.compose.ui.text.font.FontStyle, androidx.compose.ui.text.font.FontSynthesis, androidx.compose.ui.text.font.FontFamily, String, long, androidx.compose.ui.text.style.BaselineShift, androidx.compose.ui.text.style.TextGeometricTransform, androidx.compose.ui.text.intl.LocaleList, long, androidx.compose.ui.text.style.TextDecoration, androidx.compose.ui.graphics.Shadow, androidx.compose.ui.text.style.TextAlign, androidx.compose.ui.text.style.TextDirection, long, androidx.compose.ui.text.style.TextIndent) parameter #6:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter fontFeatureSettings in androidx.compose.ui.text.TextStyle.copy(long color, long fontSize, androidx.compose.ui.text.font.FontWeight fontWeight, androidx.compose.ui.text.font.FontStyle fontStyle, androidx.compose.ui.text.font.FontSynthesis fontSynthesis, androidx.compose.ui.text.font.FontFamily fontFamily, String fontFeatureSettings, long letterSpacing, androidx.compose.ui.text.style.BaselineShift baselineShift, androidx.compose.ui.text.style.TextGeometricTransform textGeometricTransform, androidx.compose.ui.text.intl.LocaleList localeList, long background, androidx.compose.ui.text.style.TextDecoration textDecoration, androidx.compose.ui.graphics.Shadow shadow, androidx.compose.ui.text.style.TextAlign textAlign, androidx.compose.ui.text.style.TextDirection textDirection, long lineHeight, androidx.compose.ui.text.style.TextIndent textIndent)
-InvalidNullConversion: androidx.compose.ui.text.TextStyle#copy(long, long, androidx.compose.ui.text.font.FontWeight, androidx.compose.ui.text.font.FontStyle, androidx.compose.ui.text.font.FontSynthesis, androidx.compose.ui.text.font.FontFamily, String, long, androidx.compose.ui.text.style.BaselineShift, androidx.compose.ui.text.style.TextGeometricTransform, androidx.compose.ui.text.intl.LocaleList, long, androidx.compose.ui.text.style.TextDecoration, androidx.compose.ui.graphics.Shadow, androidx.compose.ui.text.style.TextAlign, androidx.compose.ui.text.style.TextDirection, long, androidx.compose.ui.text.style.TextIndent, androidx.compose.ui.text.PlatformTextStyle, androidx.compose.ui.text.style.LineHeightStyle) parameter #6:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter fontFeatureSettings in androidx.compose.ui.text.TextStyle.copy(long color, long fontSize, androidx.compose.ui.text.font.FontWeight fontWeight, androidx.compose.ui.text.font.FontStyle fontStyle, androidx.compose.ui.text.font.FontSynthesis fontSynthesis, androidx.compose.ui.text.font.FontFamily fontFamily, String fontFeatureSettings, long letterSpacing, androidx.compose.ui.text.style.BaselineShift baselineShift, androidx.compose.ui.text.style.TextGeometricTransform textGeometricTransform, androidx.compose.ui.text.intl.LocaleList localeList, long background, androidx.compose.ui.text.style.TextDecoration textDecoration, androidx.compose.ui.graphics.Shadow shadow, androidx.compose.ui.text.style.TextAlign textAlign, androidx.compose.ui.text.style.TextDirection textDirection, long lineHeight, androidx.compose.ui.text.style.TextIndent textIndent, androidx.compose.ui.text.PlatformTextStyle platformStyle, androidx.compose.ui.text.style.LineHeightStyle lineHeightStyle)
diff --git a/compose/ui/ui-text/api/restricted_current.txt b/compose/ui/ui-text/api/restricted_current.txt
index bb94921..df13e31 100644
--- a/compose/ui/ui-text/api/restricted_current.txt
+++ b/compose/ui/ui-text/api/restricted_current.txt
@@ -22,6 +22,7 @@
     method public java.util.List<androidx.compose.ui.text.AnnotatedString.Range<java.lang.String>> getStringAnnotations(int start, int end);
     method public String getText();
     method public java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.TtsAnnotation>> getTtsAnnotations(int start, int end);
+    method public boolean hasStringAnnotations(String tag, int start, int end);
     method @androidx.compose.runtime.Stable public operator androidx.compose.ui.text.AnnotatedString plus(androidx.compose.ui.text.AnnotatedString other);
     method public androidx.compose.ui.text.AnnotatedString subSequence(int startIndex, int endIndex);
     method public androidx.compose.ui.text.AnnotatedString subSequence(long range);
@@ -513,11 +514,11 @@
 
   @androidx.compose.runtime.Immutable public final class TextStyle {
     ctor public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens);
-    ctor @Deprecated public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent);
-    ctor @Deprecated public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle);
+    ctor @Deprecated public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent);
+    ctor @Deprecated public TextStyle(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle);
     method public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle, optional androidx.compose.ui.text.style.LineBreak? lineBreak, optional androidx.compose.ui.text.style.Hyphens? hyphens);
-    method @Deprecated public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent);
-    method @Deprecated public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle);
+    method @Deprecated public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent);
+    method @Deprecated public androidx.compose.ui.text.TextStyle copy(optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontSynthesis? fontSynthesis, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional String? fontFeatureSettings, optional long letterSpacing, optional androidx.compose.ui.text.style.BaselineShift? baselineShift, optional androidx.compose.ui.text.style.TextGeometricTransform? textGeometricTransform, optional androidx.compose.ui.text.intl.LocaleList? localeList, optional long background, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.graphics.Shadow? shadow, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional androidx.compose.ui.text.style.TextDirection? textDirection, optional long lineHeight, optional androidx.compose.ui.text.style.TextIndent? textIndent, optional androidx.compose.ui.text.PlatformTextStyle? platformStyle, optional androidx.compose.ui.text.style.LineHeightStyle? lineHeightStyle);
     method public long getBackground();
     method public androidx.compose.ui.text.style.BaselineShift? getBaselineShift();
     method public long getColor();
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidAccessibilitySpannableString.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidAccessibilitySpannableString.android.kt
index c3e09fa..cd3a8f2 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidAccessibilitySpannableString.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidAccessibilitySpannableString.android.kt
@@ -72,7 +72,7 @@
     fontFamilyResolver: FontFamily.Resolver
 ): SpannableString {
     val spannableString = SpannableString(text)
-    spanStyles.fastForEach { (style, start, end) ->
+    spanStylesOrNull?.fastForEach { (style, start, end) ->
         // b/232238615 looking up fonts inside of accessibility does not honor overwritten
         // FontFamilyResolver. This is not safe until Font.ResourceLoader is fully removed.
         val noFontStyle = style.copy(fontFamily = null)
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/AnnotatedString.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/AnnotatedString.kt
index 5deccde..d447d62 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/AnnotatedString.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/AnnotatedString.kt
@@ -21,6 +21,7 @@
 import androidx.compose.ui.text.AnnotatedString.Builder
 import androidx.compose.ui.text.AnnotatedString.Range
 import androidx.compose.ui.text.intl.LocaleList
+import androidx.compose.ui.util.fastAny
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastMap
 
@@ -31,10 +32,23 @@
 @Immutable
 class AnnotatedString internal constructor(
     val text: String,
-    val spanStyles: List<Range<SpanStyle>> = emptyList(),
-    val paragraphStyles: List<Range<ParagraphStyle>> = emptyList(),
-    internal val annotations: List<Range<out Any>> = emptyList()
+    internal val spanStylesOrNull: List<Range<SpanStyle>>? = null,
+    internal val paragraphStylesOrNull: List<Range<ParagraphStyle>>? = null,
+    internal val annotations: List<Range<out Any>>? = null
 ) : CharSequence {
+
+    /**
+     * All [SpanStyle] that have been applied to a range of this String
+     */
+    val spanStyles: List<Range<SpanStyle>>
+        get() = spanStylesOrNull ?: emptyList()
+
+    /**
+     * All [ParagraphStyle] that have been applied to a range of this String
+     */
+    val paragraphStyles: List<Range<ParagraphStyle>>
+        get() = paragraphStylesOrNull ?: emptyList()
+
     /**
      * The basic data structure of text with multiple styles. To construct an [AnnotatedString]
      * you can use [Builder].
@@ -59,11 +73,17 @@
         text: String,
         spanStyles: List<Range<SpanStyle>> = listOf(),
         paragraphStyles: List<Range<ParagraphStyle>> = listOf()
-    ) : this(text, spanStyles, paragraphStyles, listOf())
+    ) : this(
+        text,
+        spanStyles.ifEmpty { null },
+        paragraphStyles.ifEmpty { null },
+        null
+    )
 
     init {
         var lastStyleEnd = -1
-        paragraphStyles.sortedBy { it.start }.fastForEach { paragraphStyle ->
+        @Suppress("ListIterator")
+        paragraphStylesOrNull?.sortedBy { it.start }?.fastForEach { paragraphStyle ->
             require(paragraphStyle.start >= lastStyleEnd) {
                 "ParagraphStyle should not overlap"
             }
@@ -95,8 +115,8 @@
         val text = text.substring(startIndex, endIndex)
         return AnnotatedString(
             text = text,
-            spanStyles = filterRanges(spanStyles, startIndex, endIndex),
-            paragraphStyles = filterRanges(paragraphStyles, startIndex, endIndex),
+            spanStylesOrNull = filterRanges(spanStylesOrNull, startIndex, endIndex),
+            paragraphStylesOrNull = filterRanges(paragraphStylesOrNull, startIndex, endIndex),
             annotations = filterRanges(annotations, startIndex, endIndex)
         )
     }
@@ -136,9 +156,17 @@
      */
     @Suppress("UNCHECKED_CAST")
     fun getStringAnnotations(tag: String, start: Int, end: Int): List<Range<String>> =
-        annotations.fastFilter {
+        (annotations?.fastFilter {
             it.item is String && tag == it.tag && intersect(start, end, it.start, it.end)
-        } as List<Range<String>>
+        } ?: emptyList()) as List<Range<String>>
+
+    /**
+     * Returns true if [getStringAnnotations] with the same parameters would return a non-empty list
+     */
+    fun hasStringAnnotations(tag: String, start: Int, end: Int): Boolean =
+        annotations?.fastAny {
+            it.item is String && tag == it.tag && intersect(start, end, it.start, it.end)
+        } ?: false
 
     /**
      * Query all of the string annotations attached on this AnnotatedString.
@@ -151,9 +179,9 @@
      */
     @Suppress("UNCHECKED_CAST")
     fun getStringAnnotations(start: Int, end: Int): List<Range<String>> =
-        annotations.fastFilter {
+        (annotations?.fastFilter {
             it.item is String && intersect(start, end, it.start, it.end)
-        } as List<Range<String>>
+        } ?: emptyList()) as List<Range<String>>
 
     /**
      * Query all of the [TtsAnnotation]s attached on this [AnnotatedString].
@@ -166,9 +194,9 @@
      */
     @Suppress("UNCHECKED_CAST")
     fun getTtsAnnotations(start: Int, end: Int): List<Range<TtsAnnotation>> =
-        annotations.fastFilter {
+        ((annotations?.fastFilter {
             it.item is TtsAnnotation && intersect(start, end, it.start, it.end)
-        } as List<Range<TtsAnnotation>>
+        } ?: emptyList()) as List<Range<TtsAnnotation>>)
 
     /**
      * Query all of the [UrlAnnotation]s attached on this [AnnotatedString].
@@ -182,25 +210,25 @@
     @ExperimentalTextApi
     @Suppress("UNCHECKED_CAST")
     fun getUrlAnnotations(start: Int, end: Int): List<Range<UrlAnnotation>> =
-        annotations.fastFilter {
+        ((annotations?.fastFilter {
             it.item is UrlAnnotation && intersect(start, end, it.start, it.end)
-        } as List<Range<UrlAnnotation>>
+        } ?: emptyList()) as List<Range<UrlAnnotation>>)
 
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         if (other !is AnnotatedString) return false
         if (text != other.text) return false
-        if (spanStyles != other.spanStyles) return false
-        if (paragraphStyles != other.paragraphStyles) return false
+        if (spanStylesOrNull != other.spanStylesOrNull) return false
+        if (paragraphStylesOrNull != other.paragraphStylesOrNull) return false
         if (annotations != other.annotations) return false
         return true
     }
 
     override fun hashCode(): Int {
         var result = text.hashCode()
-        result = 31 * result + spanStyles.hashCode()
-        result = 31 * result + paragraphStyles.hashCode()
-        result = 31 * result + annotations.hashCode()
+        result = 31 * result + (spanStylesOrNull?.hashCode() ?: 0)
+        result = 31 * result + (paragraphStylesOrNull?.hashCode() ?: 0)
+        result = 31 * result + (annotations?.hashCode() ?: 0)
         return result
     }
 
@@ -365,14 +393,14 @@
             val start = this.text.length
             this.text.append(text.text)
             // offset every style with start and add to the builder
-            text.spanStyles.fastForEach {
+            text.spanStylesOrNull?.fastForEach {
                 addStyle(it.item, start + it.start, start + it.end)
             }
-            text.paragraphStyles.fastForEach {
+            text.paragraphStylesOrNull?.fastForEach {
                 addStyle(it.item, start + it.start, start + it.end)
             }
 
-            text.annotations.fastForEach {
+            text.annotations?.fastForEach {
                 annotations.add(
                     MutableRange(it.item, start + it.start, start + it.end, it.tag)
                 )
@@ -392,14 +420,14 @@
             val insertionStart = this.text.length
             this.text.append(text.text, start, end)
             // offset every style with insertionStart and add to the builder
-            text.getLocalSpanStyles(start, end).fastForEach {
+            text.getLocalSpanStyles(start, end)?.fastForEach {
                 addStyle(it.item, insertionStart + it.start, insertionStart + it.end)
             }
-            text.getLocalParagraphStyles(start, end).fastForEach {
+            text.getLocalParagraphStyles(start, end)?.fastForEach {
                 addStyle(it.item, insertionStart + it.start, insertionStart + it.end)
             }
 
-            text.getLocalAnnotations(start, end).fastForEach {
+            text.getLocalAnnotations(start, end)?.fastForEach {
                 annotations.add(
                     MutableRange(
                         it.item,
@@ -608,9 +636,15 @@
         fun toAnnotatedString(): AnnotatedString {
             return AnnotatedString(
                 text = text.toString(),
-                spanStyles = spanStyles.fastMap { it.toRange(text.length) },
-                paragraphStyles = paragraphStyles.fastMap { it.toRange(text.length) },
-                annotations = annotations.fastMap { it.toRange(text.length) }
+                spanStylesOrNull = spanStyles
+                    .fastMap { it.toRange(text.length) }
+                    .ifEmpty { null },
+                paragraphStylesOrNull = paragraphStyles
+                    .fastMap { it.toRange(text.length) }
+                    .ifEmpty { null },
+                annotations = annotations
+                    .fastMap { it.toRange(text.length) }
+                    .ifEmpty { null }
             )
         }
     }
@@ -635,7 +669,7 @@
     defaultParagraphStyle: ParagraphStyle
 ): List<Range<ParagraphStyle>> {
     val length = text.length
-    val paragraphStyles = paragraphStyles
+    val paragraphStyles = paragraphStylesOrNull ?: emptyList()
 
     var lastOffset = 0
     val result = mutableListOf<Range<ParagraphStyle>>()
@@ -668,8 +702,9 @@
 private fun AnnotatedString.getLocalSpanStyles(
     start: Int,
     end: Int
-): List<Range<SpanStyle>> {
-    if (start == end) return listOf()
+): List<Range<SpanStyle>>? {
+    if (start == end) return null
+    val spanStyles = spanStylesOrNull ?: return null
     // If the given range covers the whole AnnotatedString, return SpanStyles without conversion.
     if (start == 0 && end >= this.text.length) {
         return spanStyles
@@ -694,8 +729,9 @@
 private fun AnnotatedString.getLocalParagraphStyles(
     start: Int,
     end: Int
-): List<Range<ParagraphStyle>> {
-    if (start == end) return listOf()
+): List<Range<ParagraphStyle>>? {
+    if (start == end) return null
+    val paragraphStyles = paragraphStylesOrNull ?: return null
     // If the given range covers the whole AnnotatedString, return SpanStyles without conversion.
     if (start == 0 && end >= this.text.length) {
         return paragraphStyles
@@ -720,8 +756,9 @@
 private fun AnnotatedString.getLocalAnnotations(
     start: Int,
     end: Int
-): List<Range<out Any>> {
-    if (start == end) return listOf()
+): List<Range<out Any>>? {
+    if (start == end) return null
+    val annotations = annotations ?: return null
     // If the given range covers the whole AnnotatedString, return SpanStyles without conversion.
     if (start == 0 && end >= this.text.length) {
         return annotations
@@ -752,7 +789,7 @@
 ): AnnotatedString {
     return AnnotatedString(
         text = if (start != end) text.substring(start, end) else "",
-        spanStyles = getLocalSpanStyles(start, end)
+        spanStylesOrNull = getLocalSpanStyles(start, end)
     )
 }
 
@@ -1005,16 +1042,18 @@
  * @param start the inclusive start offset of the text range
  * @param end the exclusive end offset of the text range
  */
-private fun <T> filterRanges(ranges: List<Range<out T>>, start: Int, end: Int): List<Range<T>> {
+private fun <T> filterRanges(ranges: List<Range<out T>>?, start: Int, end: Int): List<Range<T>>? {
     require(start <= end) { "start ($start) should be less than or equal to end ($end)" }
-    return ranges.fastFilter { intersect(start, end, it.start, it.end) }.fastMap {
+    val nonNullRange = ranges ?: return null
+
+    return nonNullRange.fastFilter { intersect(start, end, it.start, it.end) }.fastMap {
         Range(
             item = it.item,
             start = maxOf(start, it.start) - start,
             end = minOf(end, it.end) - start,
             tag = it.tag
         )
-    }
+    }.ifEmpty { null }
 }
 
 /**
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/Savers.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/Savers.kt
index e23240e..c5ad4ce 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/Savers.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/Savers.kt
@@ -81,11 +81,16 @@
     },
     restore = {
         val list = it as List<Any?>
+        // lift these to make types work
+        val spanStylesOrNull: List<AnnotatedString.Range<SpanStyle>>? =
+            restore(list[1], AnnotationRangeListSaver)
+        val paragraphStylesOrNull: List<AnnotatedString.Range<ParagraphStyle>>? =
+            restore(list[2], AnnotationRangeListSaver)
         AnnotatedString(
             text = restore(list[0])!!,
-            spanStyles = restore(list[1], AnnotationRangeListSaver)!!,
-            paragraphStyles = restore(list[2], AnnotationRangeListSaver)!!,
-            annotations = restore(list[3], AnnotationRangeListSaver)!!,
+            spanStylesOrNull = spanStylesOrNull?.ifEmpty { null },
+            paragraphStylesOrNull = paragraphStylesOrNull?.ifEmpty { null },
+            annotations = restore(list[3], AnnotationRangeListSaver),
         )
     }
 )
diff --git a/compose/ui/ui-text/src/jvmMain/kotlin/androidx/compose/ui/text/JvmAnnotatedString.jvm.kt b/compose/ui/ui-text/src/jvmMain/kotlin/androidx/compose/ui/text/JvmAnnotatedString.jvm.kt
index a2f6c8d..09a611f 100644
--- a/compose/ui/ui-text/src/jvmMain/kotlin/androidx/compose/ui/text/JvmAnnotatedString.jvm.kt
+++ b/compose/ui/ui-text/src/jvmMain/kotlin/androidx/compose/ui/text/JvmAnnotatedString.jvm.kt
@@ -31,8 +31,8 @@
     transform: (String, Int, Int) -> String
 ): AnnotatedString {
     val transitions = sortedSetOf(0, text.length)
-    collectRangeTransitions(spanStyles, transitions)
-    collectRangeTransitions(paragraphStyles, transitions)
+    collectRangeTransitions(spanStylesOrNull, transitions)
+    collectRangeTransitions(paragraphStylesOrNull, transitions)
     collectRangeTransitions(annotations, transitions)
 
     var resultStr = ""
@@ -42,21 +42,21 @@
         offsetMap.put(end, resultStr.length)
     }
 
-    val newSpanStyles = spanStyles.fastMap {
+    val newSpanStyles = spanStylesOrNull?.fastMap {
         // The offset map must have mapping entry from all style start, end position.
         Range(it.item, offsetMap[it.start]!!, offsetMap[it.end]!!)
     }
-    val newParaStyles = paragraphStyles.fastMap {
+    val newParaStyles = paragraphStylesOrNull?.fastMap {
         Range(it.item, offsetMap[it.start]!!, offsetMap[it.end]!!)
     }
-    val newAnnotations = annotations.fastMap {
+    val newAnnotations = annotations?.fastMap {
         Range(it.item, offsetMap[it.start]!!, offsetMap[it.end]!!)
     }
 
     return AnnotatedString(
         text = resultStr,
-        spanStyles = newSpanStyles,
-        paragraphStyles = newParaStyles,
+        spanStylesOrNull = newSpanStyles,
+        paragraphStylesOrNull = newParaStyles,
         annotations = newAnnotations
     )
 }
@@ -68,10 +68,10 @@
  * @param target The output list
  */
 private fun collectRangeTransitions(
-    ranges: List<Range<*>>,
+    ranges: List<Range<*>>?,
     target: SortedSet<Int>
 ) {
-    ranges.fastFold(target) { acc, range ->
+    ranges?.fastFold(target) { acc, range ->
         acc.apply {
             add(range.start)
             add(range.end)
diff --git a/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/AnnotatedStringBuilderTest.kt b/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/AnnotatedStringBuilderTest.kt
index 57e115b..16c64c8 100644
--- a/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/AnnotatedStringBuilderTest.kt
+++ b/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/AnnotatedStringBuilderTest.kt
@@ -184,10 +184,10 @@
         val text = "a"
         val annotatedString = AnnotatedString(
             text = text,
-            spanStyles = listOf(
+            spanStylesOrNull = listOf(
                 text.inclusiveRangeOf('a', 'a', item = SpanStyle(color = Color.Red))
             ),
-            paragraphStyles = listOf(
+            paragraphStylesOrNull = listOf(
                 text.inclusiveRangeOf('a', 'a', item = ParagraphStyle(lineHeight = 20.sp))
             ),
             annotations = listOf(
@@ -222,8 +222,8 @@
         )
         val appendedAnnotatedString = AnnotatedString(
             text = appendedText,
-            spanStyles = appendedSpanStyles,
-            paragraphStyles = appendedParagraphStyles,
+            spanStylesOrNull = appendedSpanStyles,
+            paragraphStylesOrNull = appendedParagraphStyles,
             annotations = appendedAnnotations
         )
 
@@ -850,6 +850,35 @@
     }
 
     @Test
+    fun hasStringAnnotationTrue() {
+        val text = "Test"
+        val annotation = "Annotation"
+        val tag = "tag"
+        val buildResult = AnnotatedString.Builder().apply {
+            pushStringAnnotation(tag, annotation)
+            append(text)
+            pop()
+        }.toAnnotatedString()
+
+        assertThat(buildResult.hasStringAnnotations(tag, 0, text.length)).isTrue()
+    }
+
+    @Test
+    fun hasStringAnnotationFalse() {
+        val text = "Test"
+        val annotation = "Annotation"
+        val tag = "tag"
+        val buildResult = AnnotatedString.Builder().apply {
+            pushStringAnnotation(tag, annotation)
+            append(text)
+            pop()
+            append(text)
+        }.toAnnotatedString()
+
+        assertThat(buildResult.hasStringAnnotations(tag, text.length, buildResult.length)).isFalse()
+    }
+
+    @Test
     fun pushAnnotation_multiple_nested() {
         val annotation1 = "Annotation1"
         val annotation2 = "Annotation2"
diff --git a/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/AnnotatedStringTest.kt b/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/AnnotatedStringTest.kt
index e73d220..05297b1 100644
--- a/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/AnnotatedStringTest.kt
+++ b/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/AnnotatedStringTest.kt
@@ -113,8 +113,8 @@
         )
         val annotatedString1 = AnnotatedString(
             text = text1,
-            spanStyles = spanStyles1,
-            paragraphStyles = paragraphStyles1,
+            spanStylesOrNull = spanStyles1,
+            paragraphStylesOrNull = paragraphStyles1,
             annotations = annotations1
         )
 
@@ -123,8 +123,8 @@
         val paragraphStyle = ParagraphStyle(lineHeight = 10.sp)
         val annotatedString2 = AnnotatedString(
             text = text2,
-            spanStyles = listOf(Range(spanStyle, 0, text2.length)),
-            paragraphStyles = listOf(Range(paragraphStyle, 0, text2.length)),
+            spanStylesOrNull = listOf(Range(spanStyle, 0, text2.length)),
+            paragraphStylesOrNull = listOf(Range(paragraphStyle, 0, text2.length)),
             annotations = listOf(Range("annotation2", 0, text2.length, "scope2"))
         )
 
diff --git a/compose/ui/ui-tooling/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/animation/clock/TransitionClockTest.kt b/compose/ui/ui-tooling/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/animation/clock/TransitionClockTest.kt
index 5f0b0be..27dc911 100644
--- a/compose/ui/ui-tooling/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/animation/clock/TransitionClockTest.kt
+++ b/compose/ui/ui-tooling/src/androidAndroidTest/kotlin/androidx/compose/ui/tooling/animation/clock/TransitionClockTest.kt
@@ -21,7 +21,9 @@
 import androidx.compose.animation.Crossfade
 import androidx.compose.animation.ExperimentalAnimationApi
 import androidx.compose.animation.animateColor
+import androidx.compose.animation.core.ExperimentalTransitionApi
 import androidx.compose.animation.core.animateDp
+import androidx.compose.animation.core.createChildTransition
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.core.updateTransition
 import androidx.compose.animation.tooling.ComposeAnimatedProperty
@@ -30,6 +32,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.tooling.animation.AnimatedContentComposeAnimation.Companion.parseAnimatedContent
@@ -40,6 +43,7 @@
 import androidx.compose.ui.tooling.animation.states.ComposeAnimationState
 import androidx.compose.ui.tooling.animation.states.TargetState
 import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotNull
@@ -496,6 +500,68 @@
         return clock
     }
 
+    @Test
+    fun childTransition() {
+        val search = AnimationSearch.TransitionSearch { }
+        rule.searchForAnimation(search) { childTransitions() }
+        val clock = TransitionClock(search.animations.first().parse()!!)
+
+        rule.runOnIdle {
+            clock.getTransitions(100).let {
+                assertEquals(5, it.size)
+                assertEquals("Parent", it[0].label)
+                assertEquals("Child1", it[1].label)
+                assertEquals("Grandchild", it[2].label)
+                assertEquals("GrandGrandchild", it[3].label)
+                assertEquals("Child2", it[4].label)
+            }
+            clock.getAnimatedProperties().let {
+                assertEquals(5, it.size)
+                assertEquals("Parent", it[0].label)
+                assertEquals("Child1", it[1].label)
+                assertEquals("Grandchild", it[2].label)
+                assertEquals("GrandGrandchild", it[3].label)
+                assertEquals("Child2", it[4].label)
+            }
+        }
+    }
+
+    @OptIn(ExperimentalTransitionApi::class)
+    @Composable
+    fun childTransitions() {
+        val state by remember { mutableStateOf(EnumState.One) }
+        val parentTransition = updateTransition(state, label = "parent")
+        parentTransition.animateDp(
+            transitionSpec = { tween(durationMillis = 1000, delayMillis = 100) },
+            label = "Parent"
+        ) { 10.dp }
+
+        val child = parentTransition.createChildTransition(label = "child1") { it }.apply {
+            this.animateDp(
+                transitionSpec = { tween(durationMillis = 1000, delayMillis = 100) },
+                label = "Child1"
+            ) { 10.dp }
+        }
+        val grandchild = child.createChildTransition(label = "child1") { it }.apply {
+            this.animateDp(
+                transitionSpec = { tween(durationMillis = 1000, delayMillis = 100) },
+                label = "Grandchild"
+            ) { 10.dp }
+        }
+        grandchild.createChildTransition(label = "child1") { it }.apply {
+            this.animateDp(
+                transitionSpec = { tween(durationMillis = 1000, delayMillis = 100) },
+                label = "GrandGrandchild"
+            ) { 10.dp }
+        }
+        parentTransition.createChildTransition(label = "child2") { it }.apply {
+            this.animateDp(
+                transitionSpec = { tween(durationMillis = 1000, delayMillis = 100) },
+                label = "Child2"
+            ) { 10.dp }
+        }
+    }
+
     //endregion
 
     //region AnimatedContent() animations
@@ -523,6 +589,22 @@
     }
 
     @Test
+    fun animatedContentClockStateAsList() {
+        val search = AnimationSearch.AnimatedContentSearch { }
+        val target = mutableStateOf<IntSize?>(null)
+        rule.searchForAnimation(search) { AnimatedContent(IntSize(10, 10)) { target.value = it } }
+        val clock = TransitionClock(search.animations.first().parseAnimatedContent()!!)
+        rule.runOnIdle {
+            clock.setStateParameters(listOf(20, 30), listOf(40, 50))
+            clock.setClockTime(0)
+        }
+        rule.runOnIdle {
+            assertEquals(TargetState(IntSize(20, 30), IntSize(40, 50)), clock.state)
+            assertEquals(IntSize(40, 50), target.value)
+        }
+    }
+
+    @Test
     fun animatedContentClockProperties() {
         val search = AnimationSearch.AnimatedContentSearch { }
         rule.searchForAnimation(search) { AnimatedContent(1.dp) {} }
diff --git a/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/animation/clock/TransitionClock.kt b/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/animation/clock/TransitionClock.kt
index 8149017..da45461 100644
--- a/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/animation/clock/TransitionClock.kt
+++ b/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/animation/clock/TransitionClock.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.tooling.animation.clock
 
+import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.core.Transition
 import androidx.compose.animation.tooling.ComposeAnimatedProperty
 import androidx.compose.animation.tooling.TransitionInfo
@@ -23,7 +24,7 @@
 import androidx.compose.ui.tooling.animation.states.TargetState
 
 /**
- * [ComposeAnimationClock] for [Transition] animations.
+ * [ComposeAnimationClock] for [Transition] and [AnimatedContent] animations.
  * This clock also controls extension functions such as:
  * * Transition.AnimatedVisibility
  * * Transition.Crossfade
@@ -33,6 +34,7 @@
  *  @sample androidx.compose.animation.samples.AnimatedVisibilityLazyColumnSample
  *  @sample androidx.compose.animation.samples.CrossfadeSample
  *  @sample androidx.compose.animation.samples.TransitionExtensionAnimatedContentSample
+ *  @sample androidx.compose.animation.samples.AnimateIncrementDecrementSample
  */
 internal class TransitionClock<T>(override val animation: TransitionBasedAnimation<T>) :
     ComposeAnimationClock<TransitionBasedAnimation<T>, TargetState<T>> {
@@ -46,9 +48,10 @@
             setClockTime(0)
         }
 
-    @Suppress("UNCHECKED_CAST")
     override fun setStateParameters(par1: Any, par2: Any?) {
-        state = TargetState(par1 as T, par2 as T)
+        parseParametersToValue(state.initial, par1, par2)?.let {
+            state = it
+        }
     }
 
     override fun getAnimatedProperties(): List<ComposeAnimatedProperty> {
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index ab87bf3..2ac063c 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -385,7 +385,7 @@
     method public static androidx.compose.ui.Modifier focusProperties(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusProperties,kotlin.Unit> scope);
   }
 
-  public final class FocusRequester {
+  @androidx.compose.runtime.Stable public final class FocusRequester {
     ctor public FocusRequester();
     method public boolean captureFocus();
     method public boolean freeFocus();
@@ -831,17 +831,6 @@
     property public abstract int inputMode;
   }
 
-  public interface ScrollContainerInfo {
-    method public boolean canScrollHorizontally();
-    method public boolean canScrollVertically();
-  }
-
-  public final class ScrollContainerInfoKt {
-    method public static boolean canScroll(androidx.compose.ui.input.ScrollContainerInfo);
-    method public static androidx.compose.ui.Modifier consumeScrollContainerInfo(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.ScrollContainerInfo,kotlin.Unit> consumer);
-    method public static androidx.compose.ui.Modifier provideScrollContainerInfo(androidx.compose.ui.Modifier, androidx.compose.ui.input.ScrollContainerInfo scrollContainerInfo);
-  }
-
 }
 
 package androidx.compose.ui.input.key {
@@ -1772,6 +1761,15 @@
     method public void resetTracking();
   }
 
+  public final class VelocityTracker1D {
+    ctor public VelocityTracker1D(boolean isDataDifferential);
+    method public void addDataPoint(long timeMillis, float dataPoint);
+    method public float calculateVelocity();
+    method public boolean isDataDifferential();
+    method public void resetTracking();
+    property public final boolean isDataDifferential;
+  }
+
   public final class VelocityTrackerKt {
     method public static void addPointerInputChange(androidx.compose.ui.input.pointer.util.VelocityTracker, androidx.compose.ui.input.pointer.PointerInputChange event);
   }
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index 9b79b40..b6e2c40 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -495,7 +495,7 @@
     method public void modifyFocusProperties(androidx.compose.ui.focus.FocusProperties focusProperties);
   }
 
-  public final class FocusRequester {
+  @androidx.compose.runtime.Stable public final class FocusRequester {
     ctor public FocusRequester();
     method public boolean captureFocus();
     method public boolean freeFocus();
@@ -978,17 +978,6 @@
     property public abstract int inputMode;
   }
 
-  public interface ScrollContainerInfo {
-    method public boolean canScrollHorizontally();
-    method public boolean canScrollVertically();
-  }
-
-  public final class ScrollContainerInfoKt {
-    method public static boolean canScroll(androidx.compose.ui.input.ScrollContainerInfo);
-    method public static androidx.compose.ui.Modifier consumeScrollContainerInfo(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.ScrollContainerInfo,kotlin.Unit> consumer);
-    method public static androidx.compose.ui.Modifier provideScrollContainerInfo(androidx.compose.ui.Modifier, androidx.compose.ui.input.ScrollContainerInfo scrollContainerInfo);
-  }
-
 }
 
 package androidx.compose.ui.input.key {
@@ -1948,6 +1937,15 @@
     method public void resetTracking();
   }
 
+  public final class VelocityTracker1D {
+    ctor public VelocityTracker1D(boolean isDataDifferential);
+    method public void addDataPoint(long timeMillis, float dataPoint);
+    method public float calculateVelocity();
+    method public boolean isDataDifferential();
+    method public void resetTracking();
+    property public final boolean isDataDifferential;
+  }
+
   public final class VelocityTrackerKt {
     method public static void addPointerInputChange(androidx.compose.ui.input.pointer.util.VelocityTracker, androidx.compose.ui.input.pointer.PointerInputChange event);
   }
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 9db4278..32d140b 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -385,7 +385,7 @@
     method public static androidx.compose.ui.Modifier focusProperties(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.focus.FocusProperties,kotlin.Unit> scope);
   }
 
-  public final class FocusRequester {
+  @androidx.compose.runtime.Stable public final class FocusRequester {
     ctor public FocusRequester();
     method public boolean captureFocus();
     method public boolean freeFocus();
@@ -831,17 +831,6 @@
     property public abstract int inputMode;
   }
 
-  public interface ScrollContainerInfo {
-    method public boolean canScrollHorizontally();
-    method public boolean canScrollVertically();
-  }
-
-  public final class ScrollContainerInfoKt {
-    method public static boolean canScroll(androidx.compose.ui.input.ScrollContainerInfo);
-    method public static androidx.compose.ui.Modifier consumeScrollContainerInfo(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super androidx.compose.ui.input.ScrollContainerInfo,kotlin.Unit> consumer);
-    method public static androidx.compose.ui.Modifier provideScrollContainerInfo(androidx.compose.ui.Modifier, androidx.compose.ui.input.ScrollContainerInfo scrollContainerInfo);
-  }
-
 }
 
 package androidx.compose.ui.input.key {
@@ -1772,6 +1761,15 @@
     method public void resetTracking();
   }
 
+  public final class VelocityTracker1D {
+    ctor public VelocityTracker1D(boolean isDataDifferential);
+    method public void addDataPoint(long timeMillis, float dataPoint);
+    method public float calculateVelocity();
+    method public boolean isDataDifferential();
+    method public void resetTracking();
+    property public final boolean isDataDifferential;
+  }
+
   public final class VelocityTrackerKt {
     method public static void addPointerInputChange(androidx.compose.ui.input.pointer.util.VelocityTracker, androidx.compose.ui.input.pointer.PointerInputChange event);
   }
diff --git a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/ModifiersBenchmark.kt b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/ModifiersBenchmark.kt
index 6df4dd7..226b652 100644
--- a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/ModifiersBenchmark.kt
+++ b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/ModifiersBenchmark.kt
@@ -47,7 +47,9 @@
 import androidx.compose.ui.focus.focusTarget
 import androidx.compose.ui.focus.onFocusEvent
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.semantics
@@ -132,6 +134,12 @@
                     CircleShape
                 )
             },
+            *modifier("graphicsLayer") {
+                Modifier.graphicsLayer(
+                    translationX = if (it) 1f else 2f,
+                    shape = if (it) RectangleShape else CircleShape
+                )
+            }
         )
 
         private val focusRequester = FocusRequester()
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ScrollableContainerSample.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ScrollableContainerSample.kt
deleted file mode 100644
index 7146d12..0000000
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ScrollableContainerSample.kt
+++ /dev/null
@@ -1,62 +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.compose.ui.samples
-
-import androidx.annotation.Sampled
-import androidx.compose.foundation.gestures.detectTapGestures
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.input.canScroll
-import androidx.compose.ui.input.consumeScrollContainerInfo
-import androidx.compose.ui.input.pointer.pointerInput
-import java.util.concurrent.TimeoutException
-import kotlinx.coroutines.withTimeout
-
-@Sampled
-@Composable
-fun ScrollableContainerSample() {
-    var isParentScrollable by remember { mutableStateOf({ false }) }
-
-    Column(Modifier.verticalScroll(rememberScrollState())) {
-
-        Box(modifier = Modifier.consumeScrollContainerInfo {
-            isParentScrollable = { it?.canScroll() == true }
-        }) {
-            Box(Modifier.pointerInput(Unit) {
-                detectTapGestures(
-                    onPress = {
-                        // If there is an ancestor that handles drag events, this press might
-                        // become a drag so delay any work
-                        val doWork = !isParentScrollable() || try {
-                            withTimeout(100) { tryAwaitRelease() }
-                        } catch (e: TimeoutException) {
-                            true
-                        }
-                        if (doWork) println("Do work")
-                    })
-            })
-        }
-    }
-}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawReorderingTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawReorderingTest.kt
index 568809a..f73781e 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawReorderingTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawReorderingTest.kt
@@ -46,6 +46,7 @@
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertTrue
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -436,6 +437,7 @@
         )
     }
 
+    @Ignore // b/265025605
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     fun testChangingZOrder() {
@@ -569,6 +571,7 @@
         )
     }
 
+    @Ignore // b/265025605
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     fun testChangingZOrderUncle() {
@@ -628,6 +631,7 @@
         )
     }
 
+    @Ignore // b/265025605
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     fun testChangingReorderedChildSize() {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/PainterModifierTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/PainterModifierTest.kt
index 094ea37..905dfcd 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/PainterModifierTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/PainterModifierTest.kt
@@ -94,6 +94,7 @@
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -610,6 +611,7 @@
             .assertHeightIsEqualTo(composableHeight)
     }
 
+    @Ignore // b/265030745
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     @Test
     fun testBitmapPainterScalesContent(): Unit = with(rule.density) {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/focus/FocusAwareEventPropagationTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/focus/FocusAwareEventPropagationTest.kt
index 6845b6a..7d5100e 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/focus/FocusAwareEventPropagationTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/focus/FocusAwareEventPropagationTest.kt
@@ -42,6 +42,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -182,6 +183,7 @@
         }
     }
 
+    @Ignore("b/265319988")
     @Test
     fun onFocusAwareEvent_isTriggered() {
         // Arrange.
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/nestedscroll/ScrollContainerInfoTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/nestedscroll/ScrollContainerInfoTest.kt
deleted file mode 100644
index 4bf39eb..0000000
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/nestedscroll/ScrollContainerInfoTest.kt
+++ /dev/null
@@ -1,64 +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.
- */
-
-import androidx.compose.ui.input.ScrollContainerInfo
-import androidx.compose.ui.input.canScroll
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-class ScrollContainerInfoTest {
-
-    @Test
-    fun canScroll_horizontal() {
-        val subject = Subject(horizontal = true)
-
-        assertThat(subject.canScroll()).isTrue()
-    }
-
-    @Test
-    fun canScroll_vertical() {
-        val subject = Subject(vertical = true)
-
-        assertThat(subject.canScroll()).isTrue()
-    }
-
-    @Test
-    fun canScroll_both() {
-        val subject = Subject(horizontal = true, vertical = true)
-
-        assertThat(subject.canScroll()).isTrue()
-    }
-
-    @Test
-    fun canScroll_neither() {
-        val subject = Subject(horizontal = false, vertical = false)
-
-        assertThat(subject.canScroll()).isFalse()
-    }
-
-    class Subject(
-        private val horizontal: Boolean = false,
-        private val vertical: Boolean = false,
-    ) : ScrollContainerInfo {
-        override fun canScrollHorizontally(): Boolean = horizontal
-        override fun canScrollVertically(): Boolean = vertical
-    }
-}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
index 83d6962..f443ff1 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/SubcomposeLayoutTest.kt
@@ -20,6 +20,7 @@
 import android.os.Build
 import android.widget.FrameLayout
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
@@ -2173,6 +2174,34 @@
     }
 
     @Test
+    fun subcomposeLayout_movedToDifferentGroup() {
+        var wrapped by mutableStateOf(false)
+        rule.setContent {
+            val content = remember {
+                movableContentOf {
+                    BoxWithConstraints {
+                        Spacer(
+                            modifier = Modifier.testTag(wrapped.toString()),
+                        )
+                    }
+                }
+            }
+
+            if (wrapped) {
+                Box { content() }
+            } else {
+                content()
+            }
+        }
+
+        rule.runOnIdle {
+            wrapped = !wrapped
+        }
+
+        rule.waitForIdle()
+    }
+
+    @Test
     @Ignore("b/188320755")
     fun forceMeasureOfInactiveElementFromLaunchedEffect() {
         var isActive by mutableStateOf(true)
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/TestRuleExecutesLayoutPassesWhenWaitingForIdleTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/TestRuleExecutesLayoutPassesWhenWaitingForIdleTest.kt
index acf4e1a..ec23d95 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/TestRuleExecutesLayoutPassesWhenWaitingForIdleTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/TestRuleExecutesLayoutPassesWhenWaitingForIdleTest.kt
@@ -32,6 +32,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -104,6 +105,7 @@
         }
     }
 
+    @Ignore("b/265281787")
     @Test
     fun child_AndroidView() {
         val numUpdates = 5
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
index 6bb3fb7..98804c8 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
@@ -30,7 +30,6 @@
 import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.widget.FrameLayout
-import android.widget.LinearLayout
 import android.widget.RelativeLayout
 import android.widget.TextView
 import androidx.compose.foundation.background
@@ -39,8 +38,6 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.SideEffect
@@ -55,8 +52,6 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.toArgb
-import androidx.compose.ui.input.canScroll
-import androidx.compose.ui.input.consumeScrollContainerInfo
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.platform.LocalDensity
@@ -91,7 +86,6 @@
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth.assertThat
-import kotlin.math.roundToInt
 import org.hamcrest.CoreMatchers.endsWith
 import org.hamcrest.CoreMatchers.equalTo
 import org.hamcrest.CoreMatchers.instanceOf
@@ -99,6 +93,7 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import kotlin.math.roundToInt
 
 @MediumTest
 @RunWith(AndroidJUnit4::class)
@@ -642,19 +637,11 @@
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     fun androidView_noClip() {
         rule.setContent {
-            Box(
-                Modifier
-                    .fillMaxSize()
-                    .background(Color.White)) {
+            Box(Modifier.fillMaxSize().background(Color.White)) {
                 with(LocalDensity.current) {
-                    Box(
-                        Modifier
-                            .requiredSize(150.toDp())
-                            .testTag("box")) {
+                    Box(Modifier.requiredSize(150.toDp()).testTag("box")) {
                         Box(
-                            Modifier
-                                .size(100.toDp(), 100.toDp())
-                                .align(AbsoluteAlignment.TopLeft)
+                            Modifier.size(100.toDp(), 100.toDp()).align(AbsoluteAlignment.TopLeft)
                         ) {
                             AndroidView(factory = { context ->
                                 object : View(context) {
@@ -680,92 +667,6 @@
         }
     }
 
-    @Test
-    fun scrollableViewGroup_propagates_shouldDelay() {
-        val scrollContainerInfo = mutableStateOf({ false })
-        rule.activityRule.scenario.onActivity { activity ->
-            val parentComposeView = ScrollingViewGroup(activity).apply {
-                addView(
-                    ComposeView(activity).apply {
-                        setContent {
-                            Box(modifier = Modifier.consumeScrollContainerInfo {
-                                scrollContainerInfo.value = { it?.canScroll() == true }
-                            })
-                        }
-                    })
-                }
-            activity.setContentView(parentComposeView)
-        }
-
-        rule.runOnIdle {
-            assertThat(scrollContainerInfo.value()).isTrue()
-        }
-    }
-
-    @Test
-    fun nonScrollableViewGroup_doesNotPropagate_shouldDelay() {
-        val scrollContainerInfo = mutableStateOf({ false })
-        rule.activityRule.scenario.onActivity { activity ->
-            val parentComposeView = FrameLayout(activity).apply {
-                addView(
-                    ComposeView(activity).apply {
-                        setContent {
-                            Box(modifier = Modifier.consumeScrollContainerInfo {
-                                scrollContainerInfo.value = { it?.canScroll() == true }
-                            })
-                        }
-                    })
-            }
-            activity.setContentView(parentComposeView)
-        }
-
-        rule.runOnIdle {
-            assertThat(scrollContainerInfo.value()).isFalse()
-        }
-    }
-
-    @Test
-    fun viewGroup_propagates_shouldDelayTrue() {
-        lateinit var layout: View
-        rule.setContent {
-            Column(Modifier.verticalScroll(rememberScrollState())) {
-                AndroidView(
-                    factory = {
-                        layout = LinearLayout(it)
-                        layout
-                    }
-                )
-            }
-        }
-
-        rule.runOnIdle {
-            // View#isInScrollingContainer is hidden, check the parent manually.
-            val shouldDelay = (layout.parent as ViewGroup).shouldDelayChildPressedState()
-            assertThat(shouldDelay).isTrue()
-        }
-    }
-
-    @Test
-    fun viewGroup_propagates_shouldDelayFalse() {
-        lateinit var layout: View
-        rule.setContent {
-            Column {
-                AndroidView(
-                    factory = {
-                        layout = LinearLayout(it)
-                        layout
-                    }
-                )
-            }
-        }
-
-        rule.runOnIdle {
-            // View#isInScrollingContainer is hidden, check the parent manually.
-            val shouldDelay = (layout.parent as ViewGroup).shouldDelayChildPressedState()
-            assertThat(shouldDelay).isFalse()
-        }
-    }
-
     private class StateSavingView(
         private val key: String,
         private val value: String,
@@ -797,8 +698,4 @@
             value,
             displayMetrics
         ).roundToInt()
-
-    class ScrollingViewGroup(context: Context) : FrameLayout(context) {
-        override fun shouldDelayChildPressedState() = true
-    }
 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/ComposeViewTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/ComposeViewTest.kt
index 65fe4f0..b203206 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/ComposeViewTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/ComposeViewTest.kt
@@ -73,8 +73,6 @@
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SmallTest
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
 import org.hamcrest.CoreMatchers.instanceOf
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
@@ -85,6 +83,8 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
 import kotlin.math.roundToInt
 
 @MediumTest
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index f50648d..7357ca4 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -86,8 +86,6 @@
 import androidx.compose.ui.input.InputMode.Companion.Touch
 import androidx.compose.ui.input.InputModeManager
 import androidx.compose.ui.input.InputModeManagerImpl
-import androidx.compose.ui.input.ModifierLocalScrollContainerInfo
-import androidx.compose.ui.input.ScrollContainerInfo
 import androidx.compose.ui.input.key.Key.Companion.Back
 import androidx.compose.ui.input.key.Key.Companion.DirectionCenter
 import androidx.compose.ui.input.key.Key.Companion.DirectionDown
@@ -117,7 +115,6 @@
 import androidx.compose.ui.input.rotary.onRotaryScrollEvent
 import androidx.compose.ui.layout.RootMeasurePolicy
 import androidx.compose.ui.modifier.ModifierLocalManager
-import androidx.compose.ui.modifier.ModifierLocalProvider
 import androidx.compose.ui.node.InternalCoreApi
 import androidx.compose.ui.node.LayoutNode
 import androidx.compose.ui.node.LayoutNode.UsageByParent
@@ -216,34 +213,6 @@
         false
     }
 
-    // We don't have a way to determine direction in Android, return true for both directions.
-    private val scrollContainerInfo = object : ModifierLocalProvider<ScrollContainerInfo?> {
-        override val key = ModifierLocalScrollContainerInfo
-        override val value = object : ScrollContainerInfo {
-            // Intentionally not using [View#canScrollHorizontally], to maintain semantics of
-            // View#isInScrollingContainer
-            override fun canScrollHorizontally(): Boolean =
-                view.isInScrollableViewGroup()
-
-            // Intentionally not using [View#canScrollVertically], to maintain semantics of
-            // View#isInScrollingContainer
-            override fun canScrollVertically(): Boolean =
-                view.isInScrollableViewGroup()
-
-            // Copied from View#isInScrollingContainer() which is @hide
-            private fun View.isInScrollableViewGroup(): Boolean {
-                var p = parent
-                while (p != null && p is ViewGroup) {
-                    if (p.shouldDelayChildPressedState()) {
-                        return true
-                    }
-                    p = p.parent
-                }
-                return false
-            }
-        }
-    }
-
     private val canvasHolder = CanvasHolder()
 
     override val root = LayoutNode().also {
@@ -255,7 +224,6 @@
             .then(rotaryInputModifier)
             .then(focusOwner.modifier)
             .then(keyInputModifier)
-            .then(scrollContainerInfo)
     }
 
     override val rootForTest: RootForTest = this
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt
index 285c1ce..4cf957db 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidViewHolder.android.kt
@@ -35,8 +35,6 @@
 import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.input.pointer.pointerInteropFilter
-import androidx.compose.ui.input.canScroll
-import androidx.compose.ui.input.consumeScrollContainerInfo
 import androidx.compose.ui.layout.IntrinsicMeasurable
 import androidx.compose.ui.layout.IntrinsicMeasureScope
 import androidx.compose.ui.layout.Measurable
@@ -183,8 +181,6 @@
     private val nestedScrollingParentHelper: NestedScrollingParentHelper =
         NestedScrollingParentHelper(this)
 
-    private var isInScrollContainer: () -> Boolean = { true }
-
     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
         view?.measure(widthMeasureSpec, heightMeasureSpec)
         setMeasuredDimension(view?.measuredWidth ?: 0, view?.measuredHeight ?: 0)
@@ -284,15 +280,11 @@
                     (layoutNode.owner as? AndroidComposeView)
                         ?.drawAndroidView(this@AndroidViewHolder, canvas.nativeCanvas)
                 }
-            }
-            .onGloballyPositioned {
+            }.onGloballyPositioned {
                 // The global position of this LayoutNode can change with it being replaced. For
                 // these cases, we need to inform the View.
                 layoutAccordingTo(layoutNode)
             }
-            .consumeScrollContainerInfo { scrollContainerInfo ->
-                isInScrollContainer = { scrollContainerInfo?.canScroll() == true }
-            }
         layoutNode.modifier = modifier.then(coreModifier)
         onModifierChanged = { layoutNode.modifier = it.then(coreModifier) }
 
@@ -406,7 +398,9 @@
         }
     }
 
-    override fun shouldDelayChildPressedState(): Boolean = isInScrollContainer()
+    // TODO: b/203141462 - consume whether the AndroidView() is inside a scrollable container, and
+    //  use that to set this. In the meantime set true as the defensive default.
+    override fun shouldDelayChildPressedState(): Boolean = true
 
     // NestedScrollingParent3
     override fun onStartNestedScroll(child: View, target: View, axes: Int, type: Int): Boolean {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawModifier.kt
index ee54cb1..01c13da 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/draw/DrawModifier.kt
@@ -17,18 +17,19 @@
 package androidx.compose.ui.draw
 
 import androidx.compose.runtime.remember
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
 import androidx.compose.ui.graphics.drawscope.DrawScope
-import androidx.compose.ui.platform.InspectorInfo
-import androidx.compose.ui.platform.InspectorValueInfo
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.internal.JvmDefaultWithCompatibility
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.modifierElementOf
 
 /**
  * A [Modifier.Element] that draws into the space of the layout.
@@ -83,38 +84,35 @@
 /**
  * Draw into a [Canvas] behind the modified content.
  */
+@OptIn(ExperimentalComposeUiApi::class)
 fun Modifier.drawBehind(
     onDraw: DrawScope.() -> Unit
 ) = this.then(
-    DrawBackgroundModifier(
-        onDraw = onDraw,
-        inspectorInfo = debugInspectorInfo {
+    modifierElementOf(
+        key = onDraw,
+        create = {
+            DrawBackgroundModifier(onDraw = onDraw)
+        },
+        update = {
+            it.onDraw = onDraw
+        },
+        definitions = debugInspectorInfo {
             name = "drawBehind"
             properties["onDraw"] = onDraw
         }
     )
+
 )
 
+@OptIn(ExperimentalComposeUiApi::class)
 private class DrawBackgroundModifier(
-    val onDraw: DrawScope.() -> Unit,
-    inspectorInfo: InspectorInfo.() -> Unit
-) : DrawModifier, InspectorValueInfo(inspectorInfo) {
+    var onDraw: DrawScope.() -> Unit
+) : Modifier.Node(), DrawModifierNode {
 
     override fun ContentDrawScope.draw() {
         onDraw()
         drawContent()
     }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is DrawBackgroundModifier) return false
-
-        return onDraw == other.onDraw
-    }
-
-    override fun hashCode(): Int {
-        return onDraw.hashCode()
-    }
 }
 
 /**
@@ -245,35 +243,34 @@
  * Creates a [DrawModifier] that allows the developer to draw before or after the layout's
  * contents. It also allows the modifier to adjust the layout's canvas.
  */
+@OptIn(ExperimentalComposeUiApi::class)
 fun Modifier.drawWithContent(
     onDraw: ContentDrawScope.() -> Unit
 ): Modifier = this.then(
-    DrawWithContentModifier(
-        onDraw = onDraw,
-        inspectorInfo = debugInspectorInfo {
+    modifierElementOf(
+        key = onDraw,
+        create = {
+            DrawWithContentModifier(
+                onDraw = onDraw
+            )
+        },
+        update = {
+            it.onDraw = onDraw
+        },
+        definitions = debugInspectorInfo {
             name = "drawWithContent"
             properties["onDraw"] = onDraw
         }
     )
+
 )
 
+@OptIn(ExperimentalComposeUiApi::class)
 private class DrawWithContentModifier(
-    val onDraw: ContentDrawScope.() -> Unit,
-    inspectorInfo: InspectorInfo.() -> Unit
-) : DrawModifier, InspectorValueInfo(inspectorInfo) {
+    var onDraw: ContentDrawScope.() -> Unit
+) : Modifier.Node(), DrawModifierNode {
 
     override fun ContentDrawScope.draw() {
         onDraw()
     }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is DrawWithContentModifier) return false
-
-        return onDraw == other.onDraw
-    }
-
-    override fun hashCode(): Int {
-        return onDraw.hashCode()
-    }
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
index 2c66b55..d5bbb48 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.focus
 
+import androidx.compose.runtime.Stable
 import androidx.compose.runtime.collection.MutableVector
 import androidx.compose.runtime.collection.mutableVectorOf
 import androidx.compose.ui.ExperimentalComposeUiApi
@@ -40,6 +41,7 @@
  *
  * @see androidx.compose.ui.focus.focusRequester
  */
+@Stable
 class FocusRequester {
 
     @OptIn(ExperimentalComposeUiApi::class)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerModifier.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerModifier.kt
index 493756f..5fa5bdc 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerModifier.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerModifier.kt
@@ -24,6 +24,7 @@
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.MeasureScope
 import androidx.compose.ui.node.LayoutModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.node.Nodes
 import androidx.compose.ui.node.modifierElementOf
 import androidx.compose.ui.node.requireCoordinator
@@ -363,48 +364,47 @@
     ambientShadowColor: Color = DefaultShadowColor,
     spotShadowColor: Color = DefaultShadowColor,
     compositingStrategy: CompositingStrategy = CompositingStrategy.Auto
-) = this then modifierElementOf(
-    key = GraphicsLayerParameters(
-        scaleX,
-        scaleY,
-        alpha,
-        translationX,
-        translationY,
-        shadowElevation,
-        rotationX,
-        rotationY,
-        rotationZ,
-        cameraDistance,
-        transformOrigin,
-        shape,
-        clip,
-        renderEffect,
-        ambientShadowColor,
-        spotShadowColor,
-        compositingStrategy
-    ),
-    create = {
-        SimpleGraphicsLayerModifier(
-            scaleX = scaleX,
-            scaleY = scaleY,
-            alpha = alpha,
-            translationX = translationX,
-            translationY = translationY,
-            shadowElevation = shadowElevation,
-            rotationX = rotationX,
-            rotationY = rotationY,
-            rotationZ = rotationZ,
-            cameraDistance = cameraDistance,
-            transformOrigin = transformOrigin,
-            shape = shape,
-            clip = clip,
-            renderEffect = renderEffect,
-            ambientShadowColor = ambientShadowColor,
-            spotShadowColor = spotShadowColor,
-            compositingStrategy = compositingStrategy
-        )
-    },
-    definitions = debugInspectorInfo {
+) = this then GraphicsLayerModifierNodeElement(
+    scaleX,
+    scaleY,
+    alpha,
+    translationX,
+    translationY,
+    shadowElevation,
+    rotationX,
+    rotationY,
+    rotationZ,
+    cameraDistance,
+    transformOrigin,
+    shape,
+    clip,
+    renderEffect,
+    ambientShadowColor,
+    spotShadowColor,
+    compositingStrategy
+)
+
+@ExperimentalComposeUiApi
+private class GraphicsLayerModifierNodeElement(
+    val scaleX: Float,
+    val scaleY: Float,
+    val alpha: Float,
+    val translationX: Float,
+    val translationY: Float,
+    val shadowElevation: Float,
+    val rotationX: Float,
+    val rotationY: Float,
+    val rotationZ: Float,
+    val cameraDistance: Float,
+    val transformOrigin: TransformOrigin,
+    val shape: Shape,
+    val clip: Boolean,
+    val renderEffect: RenderEffect?,
+    val ambientShadowColor: Color,
+    val spotShadowColor: Color,
+    val compositingStrategy: CompositingStrategy
+) : ModifierNodeElement<SimpleGraphicsLayerModifier>(
+    inspectorInfo = debugInspectorInfo {
         name = "graphicsLayer"
         properties["scaleX"] = scaleX
         properties["scaleY"] = scaleY
@@ -423,28 +423,97 @@
         properties["ambientShadowColor"] = ambientShadowColor
         properties["spotShadowColor"] = spotShadowColor
         properties["compositingStrategy"] = compositingStrategy
-    },
-    update = {
-        it.scaleX = scaleX
-        it.scaleY = scaleY
-        it.alpha = alpha
-        it.translationX = translationX
-        it.translationY = translationY
-        it.shadowElevation = shadowElevation
-        it.rotationX = rotationX
-        it.rotationY = rotationY
-        it.rotationZ = rotationZ
-        it.cameraDistance = cameraDistance
-        it.transformOrigin = transformOrigin
-        it.shape = shape
-        it.clip = clip
-        it.renderEffect = renderEffect
-        it.ambientShadowColor = ambientShadowColor
-        it.spotShadowColor = spotShadowColor
-        it.compositingStrategy = compositingStrategy
-        it.invalidateLayerBlock()
     }
-)
+) {
+    override fun create(): SimpleGraphicsLayerModifier {
+        return SimpleGraphicsLayerModifier(
+            scaleX = scaleX,
+            scaleY = scaleY,
+            alpha = alpha,
+            translationX = translationX,
+            translationY = translationY,
+            shadowElevation = shadowElevation,
+            rotationX = rotationX,
+            rotationY = rotationY,
+            rotationZ = rotationZ,
+            cameraDistance = cameraDistance,
+            transformOrigin = transformOrigin,
+            shape = shape,
+            clip = clip,
+            renderEffect = renderEffect,
+            ambientShadowColor = ambientShadowColor,
+            spotShadowColor = spotShadowColor,
+            compositingStrategy = compositingStrategy
+        )
+    }
+
+    override fun update(node: SimpleGraphicsLayerModifier): SimpleGraphicsLayerModifier {
+        node.scaleX = scaleX
+        node.scaleY = scaleY
+        node.alpha = alpha
+        node.translationX = translationX
+        node.translationY = translationY
+        node.shadowElevation = shadowElevation
+        node.rotationX = rotationX
+        node.rotationY = rotationY
+        node.rotationZ = rotationZ
+        node.cameraDistance = cameraDistance
+        node.transformOrigin = transformOrigin
+        node.shape = shape
+        node.clip = clip
+        node.renderEffect = renderEffect
+        node.ambientShadowColor = ambientShadowColor
+        node.spotShadowColor = spotShadowColor
+        node.compositingStrategy = compositingStrategy
+        node.invalidateLayerBlock()
+
+        return node
+    }
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is ModifierNodeElement<*>) return false
+        if (other !is GraphicsLayerModifierNodeElement) return false
+
+        return this.scaleX == other.scaleX &&
+            this.scaleY == other.scaleY &&
+            this.alpha == other.alpha &&
+            this.translationX == other.translationX &&
+            this.translationY == other.translationY &&
+            this.shadowElevation == other.shadowElevation &&
+            this.rotationX == other.rotationX &&
+            this.rotationY == other.rotationY &&
+            this.rotationZ == other.rotationZ &&
+            this.cameraDistance == other.cameraDistance &&
+            this.transformOrigin == other.transformOrigin &&
+            this.shape == other.shape &&
+            this.clip == other.clip &&
+            this.renderEffect == other.renderEffect &&
+            this.ambientShadowColor == other.ambientShadowColor &&
+            this.spotShadowColor == other.spotShadowColor &&
+            this.compositingStrategy == other.compositingStrategy
+    }
+
+    override fun hashCode(): Int {
+        var result = scaleX.hashCode()
+        result = 31 * result + scaleY.hashCode()
+        result = 31 * result + alpha.hashCode()
+        result = 31 * result + translationX.hashCode()
+        result = 31 * result + translationY.hashCode()
+        result = 31 * result + shadowElevation.hashCode()
+        result = 31 * result + rotationX.hashCode()
+        result = 31 * result + rotationY.hashCode()
+        result = 31 * result + rotationZ.hashCode()
+        result = 31 * result + cameraDistance.hashCode()
+        result = 31 * result + transformOrigin.hashCode()
+        result = 31 * result + shape.hashCode()
+        result = 31 * result + clip.hashCode()
+        result = 31 * result + renderEffect.hashCode()
+        result = 31 * result + ambientShadowColor.hashCode()
+        result = 31 * result + spotShadowColor.hashCode()
+        result = 31 * result + compositingStrategy.hashCode()
+        return result
+    }
+}
 
 /**
  * A [Modifier.Node] that makes content draw into a draw layer. The draw layer can be
@@ -549,26 +618,6 @@
             "block=$layerBlock)"
 }
 
-private data class GraphicsLayerParameters(
-    val scaleX: Float,
-    val scaleY: Float,
-    val alpha: Float,
-    val translationX: Float,
-    val translationY: Float,
-    val shadowElevation: Float,
-    val rotationX: Float,
-    val rotationY: Float,
-    val rotationZ: Float,
-    val cameraDistance: Float,
-    val transformOrigin: TransformOrigin,
-    val shape: Shape,
-    val clip: Boolean,
-    val renderEffect: RenderEffect?,
-    val ambientShadowColor: Color,
-    val spotShadowColor: Color,
-    val compositingStrategy: CompositingStrategy = CompositingStrategy.Auto
-)
-
 @OptIn(ExperimentalComposeUiApi::class)
 private class SimpleGraphicsLayerModifier(
     var scaleX: Float,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/ScrollContainerInfo.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/ScrollContainerInfo.kt
deleted file mode 100644
index 2e8afd7..0000000
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/ScrollContainerInfo.kt
+++ /dev/null
@@ -1,111 +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.compose.ui.input
-
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
-import androidx.compose.ui.modifier.ModifierLocalConsumer
-import androidx.compose.ui.modifier.ModifierLocalProvider
-import androidx.compose.ui.modifier.ModifierLocalReadScope
-import androidx.compose.ui.modifier.ProvidableModifierLocal
-import androidx.compose.ui.modifier.modifierLocalConsumer
-import androidx.compose.ui.modifier.modifierLocalOf
-import androidx.compose.ui.platform.debugInspectorInfo
-
-/**
- * Represents a component that handles scroll events, so that other components in the hierarchy
- * can adjust their behaviour.
- * @See [provideScrollContainerInfo] and [consumeScrollContainerInfo]
- */
-interface ScrollContainerInfo {
-    /** @return whether this component handles horizontal scroll events */
-    fun canScrollHorizontally(): Boolean
-    /** @return whether this component handles vertical scroll events */
-    fun canScrollVertically(): Boolean
-}
-
-/** @return whether this container handles either horizontal or vertical scroll events */
-fun ScrollContainerInfo.canScroll() = canScrollVertically() || canScrollHorizontally()
-
-/**
- * A modifier to query whether there are any parents in the hierarchy that handle scroll events.
- * The [ScrollContainerInfo] provided in [consumer] will recursively look for ancestors if the
- * nearest parent does not handle scroll events in the queried direction.
- * This can be used to delay UI changes in cases where a pointer event may later become a scroll,
- * cancelling any existing press or other gesture.
- *
- * @sample androidx.compose.ui.samples.ScrollableContainerSample
- */
-@OptIn(ExperimentalComposeUiApi::class)
-fun Modifier.consumeScrollContainerInfo(consumer: (ScrollContainerInfo?) -> Unit): Modifier =
-    modifierLocalConsumer {
-        consumer(ModifierLocalScrollContainerInfo.current)
-    }
-
-/**
- * A Modifier that indicates that this component handles scroll events. Use
- * [consumeScrollContainerInfo] to query whether there is a parent in the hierarchy that is
- * a [ScrollContainerInfo].
- */
-fun Modifier.provideScrollContainerInfo(scrollContainerInfo: ScrollContainerInfo): Modifier =
-    composed(
-        inspectorInfo = debugInspectorInfo {
-            name = "provideScrollContainerInfo"
-            value = scrollContainerInfo
-        }) {
-    remember(scrollContainerInfo) {
-        ScrollContainerInfoModifierLocal(scrollContainerInfo)
-    }
-}
-
-/**
- * ModifierLocal to propagate ScrollableContainer throughout the hierarchy.
- * This Modifier will recursively check for ancestor ScrollableContainers,
- * if the current ScrollableContainer does not handle scroll events in a particular direction.
- */
-private class ScrollContainerInfoModifierLocal(
-    private val scrollContainerInfo: ScrollContainerInfo,
-) : ScrollContainerInfo, ModifierLocalProvider<ScrollContainerInfo?>, ModifierLocalConsumer {
-
-    private var parent: ScrollContainerInfo? by mutableStateOf(null)
-
-    override val key: ProvidableModifierLocal<ScrollContainerInfo?> =
-        ModifierLocalScrollContainerInfo
-    override val value: ScrollContainerInfoModifierLocal = this
-
-    override fun onModifierLocalsUpdated(scope: ModifierLocalReadScope) = with(scope) {
-        parent = ModifierLocalScrollContainerInfo.current
-    }
-
-    override fun canScrollHorizontally(): Boolean {
-        return scrollContainerInfo.canScrollHorizontally() ||
-            parent?.canScrollHorizontally() == true
-    }
-
-    override fun canScrollVertically(): Boolean {
-        return scrollContainerInfo.canScrollVertically() || parent?.canScrollVertically() == true
-    }
-}
-
-internal val ModifierLocalScrollContainerInfo = modifierLocalOf<ScrollContainerInfo?> {
-    null
-}
\ No newline at end of file
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
index 482ac9b..d24885c 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
@@ -88,22 +88,47 @@
  * A velocity tracker calculating velocity in 1 dimension.
  *
  * Add displacement data points using [addDataPoint], and obtain velocity using [calculateVelocity].
+ *
+ * Note: for calculating touch-related or other 2 dimensional/planar velocities, please use
+ * [VelocityTracker], which handles velocity tracking across both X and Y dimensions at once.
  */
-internal class VelocityTracker1D(
+class VelocityTracker1D internal constructor(
     // whether the data points added to the tracker represent differential values
     // (i.e. change in the  tracked object's displacement since the previous data point).
     // If false, it means that the data points added to the tracker will be considered as absolute
     // values (e.g. positional values).
-    private val differentialDataPoints: Boolean = false,
+    val isDataDifferential: Boolean = false,
     // The velocity tracking strategy that this instance uses for all velocity calculations.
     private val strategy: Strategy = Strategy.Lsq2,
 ) {
 
     init {
-        if (differentialDataPoints && strategy.equals(Strategy.Lsq2)) {
+        if (isDataDifferential && strategy.equals(Strategy.Lsq2)) {
             throw IllegalStateException("Lsq2 not (yet) supported for differential axes")
         }
     }
+
+    /**
+     * Constructor to create a new velocity tracker. It allows to specify whether or not the tracker
+     * should consider the data ponits provided via [addDataPoint] as differential or
+     * non-differential.
+     *
+     * Differential data ponits represent change in displacement. For instance, differential data
+     * points of [2, -1, 5] represent: the object moved by "2" units, then by "-1" units, then by
+     * "5" units. An example use case for differential data points is when tracking velocity for an
+     * object whose displacements (or change in positions) over time are known.
+     *
+     * Non-differential data ponits represent position of the object whose velocity is tracked. For
+     * instance, non-differential data points of [2, -1, 5] represent: the object was at position
+     * "2", then at position "-1", then at position "5". An example use case for non-differential
+     * data points is when tracking velocity for an object whose positions on a geometrical axis
+     * over different instances of time are known.
+     *
+     * @param isDataDifferential [true] if the data ponits provided to the constructed tracker
+     * are differential. [false] otherwise.
+     */
+    constructor(isDataDifferential: Boolean) : this(isDataDifferential, Strategy.Impulse)
+
     private val minSampleSize: Int = when (strategy) {
         Strategy.Impulse -> 2
         Strategy.Lsq2 -> 3
@@ -114,7 +139,7 @@
      * result in notably different velocities than the others, so make careful choice or change of
      * strategy whenever you want to make one.
      */
-    enum class Strategy {
+    internal enum class Strategy {
         /**
          * Least squares strategy. Polynomial fit at degree 2.
          * Note that the implementation of this strategy currently supports only non-differential
@@ -133,8 +158,11 @@
     private var index: Int = 0
 
     /**
-     * Adds a data point for velocity calculation. A data point should represent a position along
-     * the tracked axis at a given time, [timeMillis].
+     * Adds a data point for velocity calculation at a given time, [timeMillis]. The data ponit
+     * represents an amount of a change in position (for differential data points), or an absolute
+     * position (for non-differential data points). Whether or not the tracker handles differential
+     * data points is decided by [isDataDifferential], which is set once and finally during
+     * the construction of the tracker.
      *
      * Use the same units for the data points provided. For example, having some data points in `cm`
      * and some in `m` will result in incorrect velocity calculations, as this method (and the
@@ -188,7 +216,7 @@
             // Multiply by "1000" to convert from units/ms to units/s
             return when (strategy) {
                 Strategy.Impulse ->
-                    calculateImpulseVelocity(dataPoints, time, differentialDataPoints) * 1000
+                    calculateImpulseVelocity(dataPoints, time, isDataDifferential) * 1000
                 Strategy.Lsq2 -> calculateLeastSquaresVelocity(dataPoints, time) * 1000
             }
         }
@@ -199,7 +227,7 @@
     }
 
     /**
-     * Clears the tracked positions added by [addDataPoint].
+     * Clears data points added by [addDataPoint].
      */
     fun resetTracking() {
         samples.fill(element = null)
@@ -487,7 +515,7 @@
 private fun calculateImpulseVelocity(
     dataPoints: List<Float>,
     time: List<Float>,
-    differentialDataPoints: Boolean
+    isDataDifferential: Boolean
 ): Float {
     val numDataPoints = dataPoints.size
     if (numDataPoints < 2) {
@@ -501,7 +529,7 @@
             // For differential data ponits, each measurement reflects the amount of change in the
             // subject's position. However, the first sample is discarded in computation because we
             // don't know the time duration over which this change has occurred.
-            if (differentialDataPoints) dataPoints[0]
+            if (isDataDifferential) dataPoints[0]
             else dataPoints[0] - dataPoints[1]
         return dataPointsDelta / (time[0] - time[1])
     }
@@ -512,7 +540,7 @@
         }
         val vPrev = kineticEnergyToVelocity(work)
         val dataPointsDelta =
-            if (differentialDataPoints) -dataPoints[i - 1]
+            if (isDataDifferential) -dataPoints[i - 1]
             else dataPoints[i] - dataPoints[i - 1]
         val vCurr = dataPointsDelta / (time[i] - time[i - 1])
         work += (vCurr - vPrev) * abs(vCurr)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeChain.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeChain.kt
index f3603db..e1d54e7 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeChain.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeChain.kt
@@ -510,14 +510,18 @@
                 val updated = next.updateUnsafe(node)
                 if (updated !== node) {
                     // if a new instance is returned, we want to detach the old one
-                    autoInvalidateRemovedNode(node)
-                    node.detach()
+                    if (node.isAttached) {
+                        autoInvalidateRemovedNode(node)
+                        node.detach()
+                    }
                     val result = replaceNode(node, updated)
-                    autoInvalidateInsertedNode(updated)
+                    if (node.isAttached) {
+                        autoInvalidateInsertedNode(updated)
+                    }
                     return result
                 } else {
                     // the node was updated. we are done.
-                    if (next.autoInvalidate) {
+                    if (next.autoInvalidate && updated.isAttached) {
                         // the modifier element is labeled as "auto invalidate", which means
                         // that since the node was updated, we need to invalidate everything
                         // relevant to it.
@@ -529,7 +533,9 @@
             node is BackwardsCompatNode -> {
                 node.element = next
                 // We always autoInvalidate BackwardsCompatNode.
-                autoInvalidateUpdatedNode(node)
+                if (node.isAttached) {
+                    autoInvalidateUpdatedNode(node)
+                }
                 return node
             }
             else -> error("Unknown Modifier.Node type")
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeKind.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeKind.kt
index 4698e4f..595c6b6 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeKind.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeKind.kt
@@ -205,6 +205,7 @@
 
 @OptIn(ExperimentalComposeUiApi::class)
 private fun autoInvalidateNode(node: Modifier.Node, phase: Int) {
+    check(node.isAttached)
     if (node.isKind(Nodes.Layout) && node is LayoutModifierNode) {
         node.invalidateMeasurements()
         if (phase == Removed) {
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker1DTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker1DTest.kt
index 83771ee..8283e89 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker1DTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker1DTest.kt
@@ -35,7 +35,7 @@
     @Test
     fun lsq2_differentialValues_unsupported() {
         assertThrows(IllegalStateException::class.java) {
-            VelocityTracker1D(differentialDataPoints = true, Strategy.Lsq2)
+            VelocityTracker1D(isDataDifferential = true, Strategy.Lsq2)
         }
     }
     @Test
@@ -154,7 +154,7 @@
     @Test
     fun resetTracking_differentialValues_impulse() {
         // Fixed velocity at 5 points per 10 milliseconds
-        val tracker = VelocityTracker1D(differentialDataPoints = true, Strategy.Impulse)
+        val tracker = VelocityTracker1D(isDataDifferential = true, Strategy.Impulse)
         tracker.addDataPoint(0, 0f)
         tracker.addDataPoint(10, 5f)
         tracker.addDataPoint(20, 10f)
@@ -169,7 +169,7 @@
     @Test
     fun resetTracking_nonDifferentialValues_impulse() {
         // Fixed velocity at 5 points per 10 milliseconds
-        val tracker = VelocityTracker1D(differentialDataPoints = false, Strategy.Impulse)
+        val tracker = VelocityTracker1D(isDataDifferential = false, Strategy.Impulse)
         tracker.addDataPoint(0, 0f)
         tracker.addDataPoint(10, 5f)
         tracker.addDataPoint(20, 10f)
@@ -184,7 +184,7 @@
     @Test
     fun resetTracking_nonDifferentialValues_lsq2() {
         // Fixed velocity at 5 points per 10 milliseconds
-        val tracker = VelocityTracker1D(differentialDataPoints = false, Strategy.Lsq2)
+        val tracker = VelocityTracker1D(isDataDifferential = false, Strategy.Lsq2)
         tracker.addDataPoint(0, 0f)
         tracker.addDataPoint(10, 5f)
         tracker.addDataPoint(20, 10f)
diff --git a/constraintlayout/constraintlayout-compose-lint/build.gradle b/constraintlayout/constraintlayout-compose-lint/build.gradle
new file mode 100644
index 0000000..31dce84
--- /dev/null
+++ b/constraintlayout/constraintlayout-compose-lint/build.gradle
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.
+ */
+
+import androidx.build.BundleInsideHelper
+import androidx.build.LibraryType
+
+plugins {
+    id("AndroidXPlugin")
+    id("kotlin")
+}
+
+BundleInsideHelper.forInsideLintJar(project)
+
+dependencies {
+    compileOnly(libs.androidLintMinApi)
+    compileOnly(libs.kotlinStdlib)
+    bundleInside(project(":compose:lint:common"))
+
+    testImplementation(libs.kotlinStdlib)
+    testImplementation(libs.androidLint)
+    testImplementation(libs.androidLintTests)
+    testImplementation(libs.junit)
+    testImplementation(libs.truth)
+    testImplementation(project(":compose:lint:common-test"))
+}
+
+androidx {
+    name = "ConstraintLayout Compose lint checks"
+    type = LibraryType.LINT
+    inceptionYear = "2022"
+    description = "Lint checks for ConstraintLayout in Compose"
+}
\ No newline at end of file
diff --git a/constraintlayout/constraintlayout-compose-lint/src/main/java/androidx/constraintlayout/compose/lint/ConstraintLayoutComposeIssueRegistry.kt b/constraintlayout/constraintlayout-compose-lint/src/main/java/androidx/constraintlayout/compose/lint/ConstraintLayoutComposeIssueRegistry.kt
new file mode 100644
index 0000000..13e6bed
--- /dev/null
+++ b/constraintlayout/constraintlayout-compose-lint/src/main/java/androidx/constraintlayout/compose/lint/ConstraintLayoutComposeIssueRegistry.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.constraintlayout.compose.lint
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.client.api.Vendor
+import com.android.tools.lint.detector.api.CURRENT_API
+
+private const val CL_COMPOSE_NEW_ISSUE = "new?component=323867&template=1023345"
+
+class ConstraintLayoutComposeIssueRegistry : IssueRegistry() {
+    override val api = 13
+
+    override val minApi = CURRENT_API
+
+    override val issues = listOf(
+        ConstraintLayoutDslDetector.IncorrectReferencesDeclarationIssue
+    )
+
+    override val vendor = Vendor(
+        feedbackUrl = "https://issuetracker.google.com/issues/$CL_COMPOSE_NEW_ISSUE",
+        identifier = "androidx.constraintlayout.compose",
+        vendorName = "Android Open Source Project",
+    )
+}
\ No newline at end of file
diff --git a/constraintlayout/constraintlayout-compose-lint/src/main/java/androidx/constraintlayout/compose/lint/ConstraintLayoutDslDetector.kt b/constraintlayout/constraintlayout-compose-lint/src/main/java/androidx/constraintlayout/compose/lint/ConstraintLayoutDslDetector.kt
new file mode 100644
index 0000000..647017d
--- /dev/null
+++ b/constraintlayout/constraintlayout-compose-lint/src/main/java/androidx/constraintlayout/compose/lint/ConstraintLayoutDslDetector.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.constraintlayout.compose.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import java.util.EnumSet
+import org.jetbrains.kotlin.psi.KtDestructuringDeclaration
+import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
+import org.jetbrains.uast.UCallExpression
+
+private const val CL_COMPOSE_PACKAGE = "androidx.constraintlayout.compose"
+private const val CONSTRAINT_SET_SCOPE_CLASS_FQ = "$CL_COMPOSE_PACKAGE.ConstraintSetScope"
+private const val MOTION_SCENE_SCOPE_CLASS_FQ = "$CL_COMPOSE_PACKAGE.MotionSceneScope"
+private const val CREATE_REFS_FOR_NAME = "createRefsFor"
+
+class ConstraintLayoutDslDetector : Detector(), SourceCodeScanner {
+    private val knownOwnersOfCreateRefsFor = setOf(
+        CONSTRAINT_SET_SCOPE_CLASS_FQ,
+        MOTION_SCENE_SCOPE_CLASS_FQ
+    )
+
+    override fun getApplicableUastTypes() = listOf(UCallExpression::class.java)
+
+    override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
+        override fun visitCallExpression(node: UCallExpression) {
+            if (node.methodName != CREATE_REFS_FOR_NAME) {
+                return
+            }
+            val destructuringDeclarationElement =
+                node.sourcePsi?.getParentOfType<KtDestructuringDeclaration>(true) ?: return
+
+            val argsGiven = node.valueArgumentCount
+            val varsReceived = destructuringDeclarationElement.entries.size
+            if (argsGiven == varsReceived) {
+                // Ids provided to call match the variables assigned, no issue
+                return
+            }
+
+            // Verify that arguments are Strings, we can't check for correctness if the argument is
+            // an array: `val (text1, text2) = createRefsFor(*iDsArray)`
+            node.valueArguments.forEach { argExpression ->
+                if (argExpression.getExpressionType()?.canonicalText != String::class.java.name) {
+                    return
+                }
+            }
+
+            // Element resolution is relatively expensive, do last
+            val classOwnerFqName = node.resolve()?.containingClass?.qualifiedName ?: return
+
+            // Make sure the method corresponds to an expected class
+            if (!knownOwnersOfCreateRefsFor.contains(classOwnerFqName)) {
+                return
+            }
+
+            context.report(
+                issue = IncorrectReferencesDeclarationIssue,
+                scope = node,
+                location = context.getNameLocation(node),
+                message = "Arguments of `$CREATE_REFS_FOR_NAME` ($argsGiven) do not match " +
+                    "assigned variables ($varsReceived)"
+            )
+        }
+    }
+
+    companion object {
+        val IncorrectReferencesDeclarationIssue = Issue.create(
+            id = "IncorrectReferencesDeclaration",
+            briefDescription = "`$CREATE_REFS_FOR_NAME(vararg ids: Any)` should have at least one" +
+                " argument and match assigned variables",
+            explanation = "`$CREATE_REFS_FOR_NAME(vararg ids: Any)` conveniently allows creating " +
+                "multiple references using destructuring. However, providing an un-equal amount " +
+                "of arguments to the assigned variables will result in unexpected behavior since" +
+                " the variables may reference a ConstrainedLayoutReference with unknown ID.",
+            category = Category.CORRECTNESS,
+            priority = 5,
+            severity = Severity.ERROR,
+            implementation = Implementation(
+                ConstraintLayoutDslDetector::class.java,
+                EnumSet.of(Scope.JAVA_FILE)
+            )
+        )
+    }
+}
\ No newline at end of file
diff --git a/constraintlayout/constraintlayout-compose-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry b/constraintlayout/constraintlayout-compose-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
new file mode 100644
index 0000000..55db80e
--- /dev/null
+++ b/constraintlayout/constraintlayout-compose-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
@@ -0,0 +1 @@
+androidx.constraintlayout.compose.lint.ConstraintLayoutComposeIssueRegistry
\ No newline at end of file
diff --git a/constraintlayout/constraintlayout-compose-lint/src/test/java/androidx/constraintlayout/compose/lint/ApiLintVersionsTest.kt b/constraintlayout/constraintlayout-compose-lint/src/test/java/androidx/constraintlayout/compose/lint/ApiLintVersionsTest.kt
new file mode 100644
index 0000000..aa8c28f
--- /dev/null
+++ b/constraintlayout/constraintlayout-compose-lint/src/test/java/androidx/constraintlayout/compose/lint/ApiLintVersionsTest.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.constraintlayout.compose.lint
+
+import com.android.tools.lint.client.api.LintClient
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ApiLintVersionsTest {
+
+    @Test
+    fun versionsCheck() {
+        LintClient.clientName = LintClient.CLIENT_UNIT_TESTS
+
+        val registry = ConstraintLayoutComposeIssueRegistry()
+        assertThat(registry.api).isEqualTo(CURRENT_API)
+        assertThat(registry.minApi).isEqualTo(10)
+    }
+}
\ No newline at end of file
diff --git a/constraintlayout/constraintlayout-compose-lint/src/test/java/androidx/constraintlayout/compose/lint/ConstraintLayoutDslDetectorTest.kt b/constraintlayout/constraintlayout-compose-lint/src/test/java/androidx/constraintlayout/compose/lint/ConstraintLayoutDslDetectorTest.kt
new file mode 100644
index 0000000..f8befa1
--- /dev/null
+++ b/constraintlayout/constraintlayout-compose-lint/src/test/java/androidx/constraintlayout/compose/lint/ConstraintLayoutDslDetectorTest.kt
@@ -0,0 +1,379 @@
+/*
+ * 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.constraintlayout.compose.lint
+
+import androidx.compose.lint.test.compiledStub
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+private const val COMPOSE_CONSTRAINTLAYOUT_FILE_PATH = "androidx/constraintlayout/compose"
+
+@RunWith(JUnit4::class)
+class ConstraintLayoutDslDetectorTest : LintDetectorTest() {
+    override fun getDetector() = ConstraintLayoutDslDetector()
+
+    override fun getIssues(): MutableList<Issue> =
+        mutableListOf(ConstraintLayoutDslDetector.IncorrectReferencesDeclarationIssue)
+
+    private val ConstraintSetScopeStub = compiledStub(
+        filename = "ConstraintSetScope.kt",
+        filepath = COMPOSE_CONSTRAINTLAYOUT_FILE_PATH,
+        checksum = 0x912b8878,
+        source = """
+            package androidx.constraintlayout.compose
+    
+            class ConstraintSetScope {
+                private var generatedCount = 0
+                private fun nextId() = "androidx.constraintlayout.id" + generatedCount++
+
+                fun createRefsFor(vararg ids: Any): ConstrainedLayoutReferences =
+                    ConstrainedLayoutReferences(arrayOf(*ids))
+                
+                inner class ConstrainedLayoutReferences internal constructor(
+                    private val ids: Array<Any>
+                ) {
+                    operator fun component1(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(0) { nextId() })
+                    operator fun component2(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(1) { nextId() })
+                    operator fun component3(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(2) { nextId() })
+                    operator fun component4(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(3) { nextId() })
+                    operator fun component5(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(4) { nextId() })
+                    operator fun component6(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(5) { nextId() })
+                    operator fun component7(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(6) { nextId() })
+                    operator fun component8(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(7) { nextId() })
+                    operator fun component9(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(8) { nextId() })
+                    operator fun component10(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(9) { nextId() })
+                    operator fun component11(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(10) { nextId() })
+                    operator fun component12(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(11) { nextId() })
+                    operator fun component13(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(12) { nextId() })
+                    operator fun component14(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(13) { nextId() })
+                    operator fun component15(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(14) { nextId() })
+                    operator fun component16(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(15) { nextId() })
+                }
+            }
+
+            class ConstrainedLayoutReference(val id: Any)
+        """.trimIndent(),
+        """
+                META-INF/main.kotlin_module:
+                H4sIAAAAAAAA/2NgYGBmYGBgBGJ2KM3AJcjFnlqRmFuQkyrEFpJaXOJdosSg
+                xQAASc3A6SsAAAA=
+                """,
+        """
+                androidx/constraintlayout/compose/ConstrainedLayoutReference.class:
+                H4sIAAAAAAAA/6VRXU8TURA9d/u1rEW2lUoBxQ9QSlEXiG8giWJMmhQ01PSl
+                T7e713Lb7S7ZvW3wrb/FX6CJRuODaXz0RxnnlhUFfPNlzpy5M2fmzvz4+fUb
+                gMd4yLDDAy8KpXfiuGEQq4jLQPn8bThQFOgfh7Fw9n4/CK8+eTkUb0QkAlfk
+                wBjsLh9yx+dBx3nZ7gpX5ZBiyO7IQKpdhlKlfjFhe63JsFwPo47TFaqtpWOH
+                B0GouJLUzDkI1cHA97cZDOmZMBmWeqHyZeB0h32HJhRRwH2nFqiISqUb52BR
+                J/dIuL2k9hWPeF9QIsPqPyb4K9LQIh2aKY88pi1cwVWGVEXzDGwLaRQYipcl
+                8jBxbQoGZhnS6kjGDLv1/9kmfTfTEarmMcxW1i43ZCjUkzXsC8U9rrjeUH+Y
+                omMybXIMrEehE6nZBnneJp14PMpbRtmwDHs8sgwzXR6PtowN9my6mLWNBfK+
+                v8saduqwcMZMazxaSJtpO6M1thjpo3Q2uWoI1XDDY/GopxgWDweBkn1RC4Yy
+                lm1fPP1zSdrMXugJhpk6ffhg0G+L6DWnHL3R0OV+k0dS8yS4clHr7IznRK1G
+                OIhc8ULqmvmkpnmpOzbpOmm9HBT1sQjvE8sS5ggNwgwxA6vEnhMahPZ6ceoL
+                ZqqfUayuf0LpwySzktRlsYE18q+f5hLOARPvVL+aXGHSoIAy5hN5BzoKZKof
+                UXp/ThOJZv40IdE8P+n6xN7DA8InFF2gvMUWUjXcqOEmWSxpc6uG27jTAotx
+                F8stZGPMxViJYcbaL8eY/wVNn9c9/QMAAA==
+                """,
+        """
+                androidx/constraintlayout/compose/ConstraintSetScope$ConstrainedLayoutReferences.class:
+                H4sIAAAAAAAA/62Y3VLbRhTH/ysbfwgDxjGO4xjqEIcYY2JsDCGE0pAEGoMh
+                FKc0Kf0StkIERs54RYbcMb3IG7QP0F70tp1pJpl2ppPhsi/Qt8n0yCggAco4
+                RAPePbva/Z/fnj2yVv737V//AChgi2FZUquNulLdzVbqKtcakqJqNel5fUej
+                ju2ndS5n7xxeKMtauVJ/KicPu+RqqTl4RX4sN2S1InMvGENwU3omZWuSupG9
+                v74pVzQvXAyeKUVVtGmGUqp0Frc310rHdW8OrjJEUjYXLpfqjY3spqyt6zo8
+                K6lqXZM0hZSzS3VtaadWu8ngUqrcBz9D31ZdqylqdvPZdpbcyg1VqmWLqtag
+                uUqFVtbO0FN5Ile2jMnLUkPalmkgw9XUSQRTT1kX2SCoADrQKSKALoqH9kTh
+                yRGG8bOFI4BuhPwQcI4WkdK129Ajwo0Iw7lTQhKAH1F9/AUGt+6bYeVMnt+3
+                /xRQsTlTlVUtx3ArNfghPk4Ikt5FY18q9VqN1tHcvplGQ3rOFyixPmEIbMha
+                SeJaUa3Ku3b5UAzgEvpFJHCZoXCWZXtxhaFDqtAqeVKVd7VilWH+jLk8eDI7
+                AriKlIgBDDJMfUzMvBiiTD0lIw9yZFhEBtcYFpJKUkoOU/DuN2ZrXB7+sJ1O
+                Hm1zkjaaFRkEhTYkas6AvINu8rqbI+lRB6VHrdIFB6ULVukxB6XHrNLjDkqP
+                W6WvOyh93So94aD0hFX6hoPSN3Tp9qOUp6/tkmP3z8gx8ZyT4rlj4nknxfPH
+                xEedFB89Jl5wUrxwTHzMSfGxY+LjToo3783ukvFUXJQ1qSppEj0ohe1nLjrY
+                Mb3w0jcyne+EXUVvUbYK1RxjvW/2YqIQFUQh+GZPpH8h2C0KPtdBn6/Lt//C
+                HX2zlxdG2O0e35u9UCAoxHwhd4g6Rlz7v3iEoHveH/TEhBHvvf0XAtk+k+03
+                2aLJbjfZAZPdYbI7TXaXyQ6a7G6THTLZ50x22GT3mOzIO3slYl7Tw/0f3bQu
+                N0WiTQ8S3R1gmP7oM8t7tpKezSf3/tqWRrNWdlRN2ZaL6jOFK+s1eeboxEpn
+                tjv1qszQVSLRpZ3tdbnxQKIxDKFSvSLVVqWGoreNTn9Z2VAlbadBdvK47uHJ
+                1eKgo6xJla1F6akhESiqqty4U5M416nFcn2nUZHnFP1a+KBxV17f2Zjd1WQ6
+                ItdVhguGp9UT/MjRydNNuSggpB9EKco/UMtDtQ8IBvXDLLW7qd1GvS5I1Fqj
+                0Xr+9mRC4msE00MvEU6/wvl05iVifzTF1qnspEEeiOhCO8JUV6gvcTARFxEH
+                mpbugDUt3b2AanO+F7Jxu+gcNKgXfdSte/+JJLxU59J/IlZyfUpVeCk+9BrJ
+                X+Efiud/Q2d83O0abxt+jTQmPX8j8+iC5xWyv9MkFx5TGYTwFjEvRpkXA/Ne
+                CoDOG6El6tS9FJQE1Un6XKHPO+4EjRuha200wos8WXrgcsZaTGEj3IINrttp
+                3KCBGyTcIOEGLbhjLeKO2+C2OY0bMnBDhBsi3JAF93qLuBM2uB6nccMGbphw
+                w4QbtuDeaBF30gbX6zRuxMCNEG6EcCMW3Jst4k7Z4Pqcxo0auFHCjRJu1IL7
+                aYu404e4Pxu4+SZu8OzZcPF03hh5ytH7aoxYY/RGGDvkvUTjPmvy9pl486fy
+                3rLjPXs62PDGDd448caJN27hnWmR97Yd79nzwYa3z+DtI94+4u2z8N5pkfeu
+                Ha/fad6EwZsg3gTxJiy8sy3yztnxik7z9hu8/cTbT7z9Ft7PW+S9Z8fb7jRv
+                0uBNEm+SeJMW3mKLvPN2vAGneQcM3gHiHWj+mXkXWuQt2fF2OM2bMnhTxJsi
+                2pSFd7FF3iU73k6nedMGb5p408SbtvDeb5F32Y63y2nejMGbId4M8WYsvF+0
+                xOvGBpUitQRS+I74n0A/IH8Pher/yoszy+Kp7y3iQvNNVEyXE++sOXEokUuc
+                PtrpX5zJVT7xWNpqip/2E/4cXVvQxHRJzF3OZQoTkzky8pOFG2J6VsQmLe4p
+                LXqFQlReg6uIB0V8SSVW9eKrIh7i0RoYx9dYW8N5jjjHNxz+ZunhiHL0cnzL
+                cZdjjuMexzxHiWOJY5mjwDHOMcExyTHFMc1xi+P2/ypvybWDGQAA
+                """,
+        """
+                androidx/constraintlayout/compose/ConstraintSetScope.class:
+                H4sIAAAAAAAA/7VUXU8bRxQ9s/5kMY5xAgGcpiTQxDaEdShN20DTglvKUgeo
+                qVAjXjrsTpyF9S7aWSN4Q+0v6F/oL2ilFqJGilAe+6Oq3jELKcbKA2pf7tw5
+                984583Hv/PX3n68BzKDOMMM9O/Ade9+wfE+GAXe80OUHfiskoLnrS2FUzwPr
+                Ily3/F2RAmPIbfM9brjcaxirW9vCClOIMSTnHM8JnzDEiqWNDBJI6ogjxRAP
+                XziS4VHtKoKzROyJ/dC0GW4US7W30uth4HgNit/sxBZajmuLIIU+HVm1g1tn
+                ylOdylOOnUaONPjurvBI40HxssRl1UhhNoM8riuRGwzZhvBEwENhV/2WFzIw
+                M4NB3OyBhiHaQ9F8N8+I4ikwpEP/NJjBe8go8DZDnxUIoq6L53LRDxgaxc1a
+                5yvQPq9yw+PnkLBr7WRSEYHwLCHpdsdqftAwtkW4pVKkwT3PD3no0CJjxQ9X
+                Wq5LWTHHlmmMMdze8UPX8YztvaZBIiLwuGuYnjqQdCyZwgcMA9YLYe1Ei9d4
+                wJuCEhnuFy+fqctrUHHdR1HHPZQY1v7rI6cwcVbhrdBxjfkg4AeEPqAiobUH
+                q88ZSt1u3yx1ATMwUNExhYcMteLVOqDbS7cb7EMdk5hhuN4lg0qGW3QcOX7W
+                PctXlO/Scxn0qsrU8ClDYvy0vftr0cs/FSG3echpC1pzL0bfDVOG2pDtELTv
+                qFmFPJvu5KeTw3u6NqTpWu7kUNfSyknryiVMDbn+CM3SdOjkcFqrsMcss5B4
+                80tSy2nLY7nESLKSrGsViuVzqRE9n0yzPGVV0nfJttN6ltM5fUSr9C5p9Wwu
+                Rl78+zc/ZlWMWNVWphltE/X/o4EK7whTL1zmm9qhzyNe9W3BcK1Gy1ZazS0R
+                fMe3XELyNd/i7gYPHDWPwEKdfhynKUxvz5EOQfNvu5RhvDN63nMX0jKmRx9Y
+                1eVSqp3p634rsMSiowSGI4qNS/R4SHUQV69MI3135MXJp++f7DLNDBrpdpEo
+                HyP9GzkaviGbbIMx1MhmThPQA53GvKquaPEykWk0jr5C9tkxruX7jzBQ/h3D
+                c+VC/Ic/MFw4wq0jvP9rB2/iX7yjEe/P5N0hRcVrUobiHZjIj79E+RUmn5Un
+                Jl6/xPQxPrpIlkS6TTZ4uiAiU94YHlH8aZR3l8aVqNDVJDeMj/FJl0t4fJGf
+                dVzCbJs/hlWyOmGTlLuEfqy1V5n4lsY64XOU+9kmYiaemPicLL5QZt7EAqqb
+                YBJf4qtN9EnoEosSSYnBtjMq8bXEWNu/I9Hbdpb+AeegSxAbCAAA
+                """
+    )
+
+    private val MotionSceneScopeStub = compiledStub(
+        filename = "MotionSceneScope.kt",
+        filepath = COMPOSE_CONSTRAINTLAYOUT_FILE_PATH,
+        checksum = 0xc89561d0,
+        source = """
+            package androidx.constraintlayout.compose
+
+            import androidx.constraintlayout.compose.ConstrainedLayoutReference
+
+            private const val UNDEFINED_NAME_PREFIX = "androidx.constraintlayout"
+
+            class MotionSceneScope {
+                /**
+                 * Count of generated ConstraintSet & Transition names.
+                 */
+                private var generatedCount = 0
+
+                /**
+                 * Count of generated ConstraintLayoutReference IDs.
+                 */
+                private var generatedIdCount = 0
+
+                private fun nextId() = UNDEFINED_NAME_PREFIX + "id" + generatedIdCount++
+
+                fun createRefsFor(vararg ids: Any): ConstrainedLayoutReferences =
+                    ConstrainedLayoutReferences(arrayOf(*ids))
+
+                inner class ConstrainedLayoutReferences internal constructor(
+                    private val ids: Array<Any>
+                ) {
+                    operator fun component1(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(0) { nextId() })
+                    operator fun component2(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(1) { nextId() })
+                    operator fun component3(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(2) { nextId() })
+                    operator fun component4(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(3) { nextId() })
+                    operator fun component5(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(4) { nextId() })
+                    operator fun component6(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(5) { nextId() })
+                    operator fun component7(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(6) { nextId() })
+                    operator fun component8(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(7) { nextId() })
+                    operator fun component9(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(8) { nextId() })
+                    operator fun component10(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(9) { nextId() })
+                    operator fun component11(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(10) { nextId() })
+                    operator fun component12(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(11) { nextId() })
+                    operator fun component13(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(12) { nextId() })
+                    operator fun component14(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(13) { nextId() })
+                    operator fun component15(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(14) { nextId() })
+                    operator fun component16(): ConstrainedLayoutReference =
+                        ConstrainedLayoutReference(ids.getOrElse(15) { nextId() })
+                }
+            }
+        """.trimIndent(),
+        """
+                META-INF/main.kotlin_module:
+                H4sIAAAAAAAA/2NgYGBmYGBgBGJ2KM3AZc6lmJiXUpSfmVKhl5yfV1xSlJiZ
+                V5KTWJlfWgIUyC3IL04VEvLNL8nMzwtOTs1LDU7OL0j1LuES5GJPrUjMLchJ
+                FWILSS0u8S5RYtBiAACRUOp0ZAAAAA==
+                """,
+        """
+                androidx/constraintlayout/compose/MotionSceneScope$ConstrainedLayoutReferences.class:
+                H4sIAAAAAAAA/62YW1PbRhTH/ysbX4QBQ4AQLgGKCRfjGN+4l4YQKAZDKE5p
+                UnoTtkIERma8giFvTB/yDdoP0D70tZ1pJpl2psPw2Ld+oUyPjAiSgzIO0YBX
+                Z9dn/+e3Z4/slf9989c/AJLYY1iT1HypqOSPo7miyrWSpKhaQXpePNRoYP+g
+                yOXoalFTimo2J6tyNlc8kEPzF55yPlN23ZCfyiVZzcncC8YQ3JWOpGhBUnei
+                D7d35ZzmhYvBM6OoijbLsDyY+fCg01uZStXpoU2G1kGbN/oyxdJOdFfWtnV5
+                HpVUtahJuiiPrhW1tcNCYZrBpeS5D36G23tFraCo0d2j/SjRyCVVKkTTqlai
+                uUqO1lXL0JJ7Juf2jMnrUknal8mRYWDwXQTTSFYX2SGoAOpQLyKABsqG9kzh
+                oVGG5HWSEUAjmvwQcIOWMKgr16BFhButDDeuSEgAfrTp/rcY3HpkhvVrxH3f
+                zlMyxfI8VVa1GMO9waEqItgLkl6HsSe5YqFAqyhv3VypJD3nK1RS3QyBHVnL
+                SFxLq3n52K4W0gH04hMRPehjiH/4or3oZ6iTcrRGHlLlYy2dZ1i6Vg0PvVsV
+                AQxgUMQdDDHMfEy+vAhThV5RiefVERExgrsM6ZASkkIRStzD0kKBy5EP2ePQ
+                5QaHaItZmkFQaCvazHsfdyxIXA9yKZxwTDhhFU46Jpy0CqccE05ZhcccEx6z
+                Co87JjxuFZ5wTHjCKjzpmPCkLlx7WeL04bzs0N0yWiEdc046ViEdd046XiGd
+                cE46USGddE46WSGdck46VSE95px0+T5szBjfeKuyJuUlTaIvQWH/yEWHNaY3
+                XvrMpTObcKzoPapPIR9jrOv0pF0U2gRRCJ6eiPQvBBtFwec6H/M1+M5euNtO
+                T+LCKLvf4js9aQoEhXZfk7uJBkZdZ794hKB72R/0tAuj3qWzFwLZPpPtN9mi
+                ya412QGTXWey6012g8kOmuxGk91ksm+Y7GaT3WKyWy/sjVbzmh6f/eimdbkp
+                EzV6kuieAMPsR59H3rOVdO6q3Pm7exrN2ThUNWVfTqtHCle2C/Lc5TmUzmLz
+                xbzM0JAhybXD/W259EgiH4amTDEnFTalkqL3jUF/VtlRJe2wRHaoUvftedQS
+                oC6rSbm9VenAkAikVVUuzRckznVmMVs8LOXkRUV/r/m880DePtxZONZkOvgW
+                VYZbRqTNd/gRoxOlmypRQJN+wKQc/0A9D119QDCoH1Kp30j9Ghp1QaLeFnnr
+                1dsy0iS+RnA4/BLNw69wc3jkJdr/KIttU1tPTh7cRAPJNtM1R2M95xPRgU6g
+                bOkBWNnSwwvIl+d7IRs3i85BTl24TcN69J9IwkvX2PCfaM+4PqVL81pn+DVC
+                v8If7oz/hvrOMbdrrCbyGsOY8vyNkSe3PK8Q/Z0mufCU2iCEN2j3IsG8uLPs
+                pQTovK20RBBZF2aIswMhevXT64K7h/xGKWE15OFFnCw9cTFjLaa0EW7SBtft
+                NG6XgdtFuF2E22XBTVWJO2aDW+M0breB20243YTbbcEdrxJ3wgbX4zRur4Hb
+                S7i9hNtrwZ2sEnfKBtfrNG6fgdtHuH2E22fBna4Sd8YG1+c0br+B20+4/eU/
+                M+6nVeLOvsX92cCNl3GD16+Gjqt5ByjSDNXAAJEO0PPewFveXvL7rMx728Qb
+                v5L3nh3v9cvBhnfI4B0i3iH9+dTCO1cl73073uvXgw1v2OANE2+YeMMW3vkq
+                eR/Y8fqd5o0YvBHijRBvxMK7UCXvoh2v6DRv1OCNEm+UeKMW3s+r5F2y4611
+                mjdm8MaIN0a8MQtvukreZTvegNO8CYM3QbwJ4k1YeFeq5M3Y8dY5zZsyeFPE
+                myLelIV3tUreNTveeqd5xw3eceIdJ95xC+/DKnnX7XgbnOadNHgniXeSeCct
+                vF9UxevGDrUi9QRS+I74n0E/IH8Pha7/ZVfn1sUrnlrElfJTqDic7bmwFsVw
+                T6znKl9nf0WmMPGep9JeWfqqn+QX6b0VTRzOiLG+2EhqcipGRnxqbFQcXhCx
+                S8s6oOVuUHKyW3Cl8SiNL6nFpt58lcZjPNkC4/gaW1u4ydHJ8Q2Hv9x6ONo4
+                uji+5XjAscixxLHMkeFY41jnSHKMcUxwTHHMcMxy3OO4/z+8pxUSTxkAAA==
+                """,
+        """
+                androidx/constraintlayout/compose/MotionSceneScope.class:
+                H4sIAAAAAAAA/61VW08bRxT+Zm1ssxjHOOHqNCWBNuYS1qH0FmhScEpZ11wK
+                LWrES4fdCVlY76KdNYI31H/Qp773F7RSC1EjRSiP/VFVzqyXpBgrUqLI0pkz
+                35z5vpkz56z//e+f5wBmsMEwzT078B370LB8T4YBd7zQ5Ud+IySgvu9LYSz7
+                oeN7G5bwxIbl74s0GEN+lx9ww+XejrG6vSusMI0EQ2rO8ZzwPkOiNLaZRQdS
+                OpJIMyTDJ45kmKm9vdws0XriMDRthmulsdpr4Y0wcLwdWu9vxRYajmuLII1u
+                HTmlXzzXnWrVdewM8iTB9/eFRxJ3SpcVLovGArNZFHBVaVyjlOzQkQMeCtu0
+                K37DCxmYmUUf+juhYYDhesl8M9OQYioyZEK/uZjFB8gq8AZDtxUIIl8Xj+Wi
+                HzCI0lat9RHopG+f4NHKeaSwa1EoaYhAeJaQlNyRmh/sGLsi3FYh0uCe54dc
+                UUhjxQ9XGq5LUQnHlhmMMNzY80PX8Yzdg7pB2iLwuGuYnrqOdCyZxkcMvdYT
+                Ye3Fm9d4wOuCAhluly7fqM1rUGXdRknHxxhjWHm/F05j4ry4qTZcYz4I+BGh
+                d6hEaO/R6mOGsXaZN8fagFkYKOuYwl2Gauldir/dG0ed9YmOScwwXG0TQcXC
+                LbqMHD1vnKV3Em/TbFl0qYrU8CVDx2izq3OvCj8u+55aXATLIuQ2DzmdSKsf
+                JOijw5ShhmR7BB06alYmz6YE/XZ2XNK1AU3X8mfHupZRTqY5EqigfE8M52g6
+                cHY8rZXZPda90PHi95SW16oj+dRQqpxe18q0VshnhvRCKsMKFFXuvEU2CtOr
+                mXzXkFbOLmnruXyCvORPL37JqTViJaijBVLHm2Z0dKy9/+4qvmGZHreVbWqP
+                0pus+LZguFKjTSuN+rYIfuDbLiGFmm9xd5MHjprHYHGd3sSpC9M7cKRD0Pzr
+                BmYYbV191Y4XwrKmR09ccbmU6lz6ht8ILLHoKIHBmGLzEj3uUqEk6Yk1+s2h
+                P/L6ojFJ6aS/B7Lf0cygkTKMjvFTZP6MwmpkUxGYxjLZbDMAndBpLKgyjDdX
+                iUyjcfgZco9OcaXQc4Le8b8wODdeTP78NwaLJ7h+gg//aOEt/I93OOb9lbyb
+                SES8Jqkp3t6JwuhTjD/D5KPxiYnnTzF9ik8vkqXQG5H1NTfEZMobwWe0vhLH
+                3aJxNW4ANckP4nN80SYJ9y7ys5YkzEb8CayR1QmbpFgTPfg+2lXFOo0/Ev4V
+                xd7fQsLEAxNfk8W8MgsmKni4BSbxDRa3kJPQJb6VSEnMRU6fxLDEksRINL0p
+                0RU55kuxHmQmPAgAAA==
+                """,
+        """
+                androidx/constraintlayout/compose/MotionSceneScopeKt.class:
+                H4sIAAAAAAAA/2WQTU8CMRCG3y7yISqC32DiQe+sGm6ejGKyEVYDSkw4mLI0
+                pLC0ZLdL9Eb8Kf4MD4Z49EcZp0ZjopfpzNN3Zt72/ePlFUANuww1rvqRlv0H
+                N9AqNhGXyoT8USeGwHiiY+E2tZFatQOhRDvQE3FpsmAMxSGfcjfkauBe9YYi
+                IJpi2Lz1z+sXnl8/v/dPm/X76xZVdwxrjV9520RSDU4Yyj/Lq3+X55BlOGjo
+                aOAOhenZm9jlSmnDrZnY9bXxkzCkIaXGSJtQKrcpDO9zw4k542mKXshsoEFs
+                ZBOH+IO02SFl/SOGvfksk5/P8k6xsF8ozmcV55DdvT3dvD1nHOJWdcxoDNb/
+                /kF1ZBhWzqxrrkyHh4lg2G0lysix8NRUxrIXitNfwwz5tk6iQFzIkKTlb2nn
+                nxBHcLBgzaOCNDJUbVvzyGGHzgzxReCLlL/iFulAXbSB9EtdpDwse1ihiIKH
+                VRQ9lLDWBYuxjo0unBjpGJufnYegJwYCAAA=
+                """
+    )
+
+    @Test
+    fun createRefsForArgumentTest() {
+        lint().files(
+            kotlin(
+                """
+                    package example
+                    
+                    import androidx.constraintlayout.compose.*
+                    
+                    fun Test() {
+                        val scopeApplier: ConstraintSetScope.() -> Unit = {
+                            val (box, text) = createRefsFor("box", "text")
+                            val (box1, text1, image1) = createRefsFor("box", "text")
+                            val (box2, text2) = createRefsFor("box", "text", "image")
+                    
+                            val ids = arrayOf("box", "text")
+                            val (box3, text3, image3) = createRefsFor(*ids)
+                        }                   
+                    }
+
+                    fun Test2() {
+                        val scopeApplier: MotionSceneScope.() -> Unit = {
+                            val (box, text) = createRefsFor("box", "text")
+                            val (box1, text1, image1) = createRefsFor("box", "text")
+                            val (box2, text2) = createRefsFor("box", "text", "image")
+                    
+                            val ids = arrayOf("box", "text")
+                            val (box3, text3, image3) = createRefsFor(*ids)
+                        }                   
+                    }
+                """.trimIndent()
+            ),
+            ConstraintSetScopeStub,
+            MotionSceneScopeStub
+        )
+            .run()
+            .expect(
+                """src/example/test.kt:8: Error: Arguments of createRefsFor (2) do not match assigned variables (3) [IncorrectReferencesDeclaration]
+        val (box1, text1, image1) = createRefsFor("box", "text")
+                                    ~~~~~~~~~~~~~
+src/example/test.kt:9: Error: Arguments of createRefsFor (3) do not match assigned variables (2) [IncorrectReferencesDeclaration]
+        val (box2, text2) = createRefsFor("box", "text", "image")
+                            ~~~~~~~~~~~~~
+src/example/test.kt:19: Error: Arguments of createRefsFor (2) do not match assigned variables (3) [IncorrectReferencesDeclaration]
+        val (box1, text1, image1) = createRefsFor("box", "text")
+                                    ~~~~~~~~~~~~~
+src/example/test.kt:20: Error: Arguments of createRefsFor (3) do not match assigned variables (2) [IncorrectReferencesDeclaration]
+        val (box2, text2) = createRefsFor("box", "text", "image")
+                            ~~~~~~~~~~~~~
+4 errors, 0 warnings"""
+            )
+    }
+}
\ No newline at end of file
diff --git a/constraintlayout/constraintlayout-compose/api/current.txt b/constraintlayout/constraintlayout-compose/api/current.txt
index edcf487..0182e04 100644
--- a/constraintlayout/constraintlayout-compose/api/current.txt
+++ b/constraintlayout/constraintlayout-compose/api/current.txt
@@ -3,6 +3,7 @@
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface BaselineAnchorable {
     method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.BaselineAnchor anchor, optional float margin, optional float goneMargin);
+    method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor anchor, optional float margin, optional float goneMargin);
   }
 
   @androidx.compose.runtime.Immutable public final class ChainStyle {
@@ -32,6 +33,7 @@
     method public androidx.constraintlayout.compose.VerticalAnchorable getAbsoluteLeft();
     method public androidx.constraintlayout.compose.VerticalAnchorable getAbsoluteRight();
     method public float getAlpha();
+    method public androidx.constraintlayout.compose.Dimension getAsDimension(float);
     method public androidx.constraintlayout.compose.BaselineAnchorable getBaseline();
     method public androidx.constraintlayout.compose.HorizontalAnchorable getBottom();
     method public androidx.constraintlayout.compose.VerticalAnchorable getEnd();
@@ -132,6 +134,7 @@
     method public final androidx.constraintlayout.compose.HorizontalChainScope constrain(androidx.constraintlayout.compose.HorizontalChainReference ref, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.HorizontalChainScope,kotlin.Unit> constrainBlock);
     method public final androidx.constraintlayout.compose.VerticalChainScope constrain(androidx.constraintlayout.compose.VerticalChainReference ref, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.VerticalChainScope,kotlin.Unit> constrainBlock);
     method public final androidx.constraintlayout.compose.ConstrainScope constrain(androidx.constraintlayout.compose.ConstrainedLayoutReference ref, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstrainScope,kotlin.Unit> constrainBlock);
+    method public final void constrain(androidx.constraintlayout.compose.ConstrainedLayoutReference![] refs, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstrainScope,kotlin.Unit> constrainBlock);
     method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createAbsoluteLeftBarrier(androidx.constraintlayout.compose.LayoutReference![] elements, optional float margin);
     method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createAbsoluteRightBarrier(androidx.constraintlayout.compose.LayoutReference![] elements, optional float margin);
     method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor createBottomBarrier(androidx.constraintlayout.compose.LayoutReference![] elements, optional float margin);
@@ -254,6 +257,26 @@
 
   @androidx.compose.foundation.layout.LayoutScopeMarker public final class ConstraintSetScope extends androidx.constraintlayout.compose.ConstraintLayoutBaseScope {
     method public androidx.constraintlayout.compose.ConstrainedLayoutReference createRefFor(Object id);
+    method public androidx.constraintlayout.compose.ConstraintSetScope.ConstrainedLayoutReferences createRefsFor(java.lang.Object... ids);
+  }
+
+  public final class ConstraintSetScope.ConstrainedLayoutReferences {
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component1();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component10();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component11();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component12();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component13();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component14();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component15();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component16();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component2();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component3();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component4();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component5();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component6();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component7();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component8();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component9();
   }
 
   public final class DesignElements {
@@ -324,6 +347,7 @@
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface HorizontalAnchorable {
     method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor anchor, optional float margin, optional float goneMargin);
+    method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.BaselineAnchor anchor, optional float margin, optional float goneMargin);
   }
 
   @androidx.compose.runtime.Stable public final class HorizontalChainReference extends androidx.constraintlayout.compose.LayoutReference {
diff --git a/constraintlayout/constraintlayout-compose/api/public_plus_experimental_current.txt b/constraintlayout/constraintlayout-compose/api/public_plus_experimental_current.txt
index 9493fdfc..05f48b7 100644
--- a/constraintlayout/constraintlayout-compose/api/public_plus_experimental_current.txt
+++ b/constraintlayout/constraintlayout-compose/api/public_plus_experimental_current.txt
@@ -8,10 +8,14 @@
   }
 
   public static final class Arc.Companion {
+    method public androidx.constraintlayout.compose.Arc getAbove();
+    method public androidx.constraintlayout.compose.Arc getBelow();
     method public androidx.constraintlayout.compose.Arc getFlip();
     method public androidx.constraintlayout.compose.Arc getNone();
     method public androidx.constraintlayout.compose.Arc getStartHorizontal();
     method public androidx.constraintlayout.compose.Arc getStartVertical();
+    property public final androidx.constraintlayout.compose.Arc Above;
+    property public final androidx.constraintlayout.compose.Arc Below;
     property public final androidx.constraintlayout.compose.Arc Flip;
     property public final androidx.constraintlayout.compose.Arc None;
     property public final androidx.constraintlayout.compose.Arc StartHorizontal;
@@ -34,6 +38,7 @@
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface BaselineAnchorable {
     method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.BaselineAnchor anchor, optional float margin, optional float goneMargin);
+    method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor anchor, optional float margin, optional float goneMargin);
   }
 
   @androidx.compose.runtime.Immutable public final class ChainStyle {
@@ -63,6 +68,7 @@
     method public androidx.constraintlayout.compose.VerticalAnchorable getAbsoluteLeft();
     method public androidx.constraintlayout.compose.VerticalAnchorable getAbsoluteRight();
     method public float getAlpha();
+    method public androidx.constraintlayout.compose.Dimension getAsDimension(float);
     method public androidx.constraintlayout.compose.BaselineAnchorable getBaseline();
     method public androidx.constraintlayout.compose.HorizontalAnchorable getBottom();
     method public androidx.constraintlayout.compose.VerticalAnchorable getEnd();
@@ -163,6 +169,7 @@
     method public final androidx.constraintlayout.compose.HorizontalChainScope constrain(androidx.constraintlayout.compose.HorizontalChainReference ref, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.HorizontalChainScope,kotlin.Unit> constrainBlock);
     method public final androidx.constraintlayout.compose.VerticalChainScope constrain(androidx.constraintlayout.compose.VerticalChainReference ref, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.VerticalChainScope,kotlin.Unit> constrainBlock);
     method public final androidx.constraintlayout.compose.ConstrainScope constrain(androidx.constraintlayout.compose.ConstrainedLayoutReference ref, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstrainScope,kotlin.Unit> constrainBlock);
+    method public final void constrain(androidx.constraintlayout.compose.ConstrainedLayoutReference![] refs, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstrainScope,kotlin.Unit> constrainBlock);
     method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createAbsoluteLeftBarrier(androidx.constraintlayout.compose.LayoutReference![] elements, optional float margin);
     method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createAbsoluteRightBarrier(androidx.constraintlayout.compose.LayoutReference![] elements, optional float margin);
     method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor createBottomBarrier(androidx.constraintlayout.compose.LayoutReference![] elements, optional float margin);
@@ -285,6 +292,26 @@
 
   @androidx.compose.foundation.layout.LayoutScopeMarker public final class ConstraintSetScope extends androidx.constraintlayout.compose.ConstraintLayoutBaseScope {
     method public androidx.constraintlayout.compose.ConstrainedLayoutReference createRefFor(Object id);
+    method public androidx.constraintlayout.compose.ConstraintSetScope.ConstrainedLayoutReferences createRefsFor(java.lang.Object... ids);
+  }
+
+  public final class ConstraintSetScope.ConstrainedLayoutReferences {
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component1();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component10();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component11();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component12();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component13();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component14();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component15();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component16();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component2();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component3();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component4();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component5();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component6();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component7();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component8();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component9();
   }
 
   @androidx.constraintlayout.compose.ExperimentalMotionApi public final class CurveFit {
@@ -393,6 +420,7 @@
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface HorizontalAnchorable {
     method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor anchor, optional float margin, optional float goneMargin);
+    method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.BaselineAnchor anchor, optional float margin, optional float goneMargin);
   }
 
   @androidx.compose.runtime.Stable public final class HorizontalChainReference extends androidx.constraintlayout.compose.LayoutReference {
@@ -594,17 +622,31 @@
     method @androidx.compose.runtime.Composable @androidx.constraintlayout.compose.ExperimentalMotionApi public static inline void MotionLayout(androidx.constraintlayout.compose.MotionScene motionScene, float progress, optional androidx.compose.ui.Modifier modifier, optional String transitionName, optional java.util.EnumSet<androidx.constraintlayout.compose.MotionLayoutDebugFlags> debug, optional int optimizationLevel, optional java.util.Set<? extends androidx.constraintlayout.compose.MotionLayoutFlag> motionLayoutFlags, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.constraintlayout.compose.ExperimentalMotionApi public static inline void MotionLayout(androidx.constraintlayout.compose.MotionScene motionScene, optional androidx.compose.ui.Modifier modifier, optional String? constraintSetName, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional java.util.EnumSet<androidx.constraintlayout.compose.MotionLayoutDebugFlags> debug, optional int optimizationLevel, optional java.util.Set<? extends androidx.constraintlayout.compose.MotionLayoutFlag> motionLayoutFlags, optional kotlin.jvm.functions.Function0<kotlin.Unit>? finishedAnimationListener, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @androidx.constraintlayout.compose.ExperimentalMotionApi public static inline void MotionLayout(androidx.constraintlayout.compose.ConstraintSet start, androidx.constraintlayout.compose.ConstraintSet end, optional androidx.compose.ui.Modifier modifier, optional androidx.constraintlayout.compose.Transition? transition, float progress, optional java.util.EnumSet<androidx.constraintlayout.compose.MotionLayoutDebugFlags> debug, optional androidx.constraintlayout.compose.LayoutInformationReceiver? informationReceiver, optional int optimizationLevel, optional java.util.Set<? extends androidx.constraintlayout.compose.MotionLayoutFlag> motionLayoutFlags, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable @androidx.constraintlayout.compose.ExperimentalMotionApi public static inline void MotionLayout(optional androidx.compose.ui.Modifier modifier, optional int optimizationLevel, androidx.constraintlayout.compose.MotionLayoutState motionLayoutState, androidx.constraintlayout.compose.MotionScene motionScene, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.constraintlayout.compose.ExperimentalMotionApi public static inline void MotionLayout(androidx.constraintlayout.compose.MotionScene motionScene, androidx.constraintlayout.compose.MotionLayoutState motionLayoutState, optional androidx.compose.ui.Modifier modifier, optional int optimizationLevel, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
   }
 
   @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.constraintlayout.compose.ExperimentalMotionApi public final class MotionLayoutScope {
-    method public long motionColor(String id, String name);
-    method public float motionDistance(String id, String name);
-    method public float motionFloat(String id, String name);
-    method public long motionFontSize(String id, String name);
-    method public int motionInt(String id, String name);
-    method @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.constraintlayout.compose.MotionLayoutScope.MotionProperties> motionProperties(String id);
-    method public androidx.constraintlayout.compose.MotionLayoutScope.MotionProperties motionProperties(String id, String tag);
+    method public long customColor(String id, String name);
+    method public float customDistance(String id, String name);
+    method public float customFloat(String id, String name);
+    method public long customFontSize(String id, String name);
+    method public int customInt(String id, String name);
+    method public androidx.constraintlayout.compose.MotionLayoutScope.CustomProperties customProperties(String id);
+    method @Deprecated public long motionColor(String id, String name);
+    method @Deprecated public float motionDistance(String id, String name);
+    method @Deprecated public float motionFloat(String id, String name);
+    method @Deprecated public long motionFontSize(String id, String name);
+    method @Deprecated public int motionInt(String id, String name);
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.runtime.State<androidx.constraintlayout.compose.MotionLayoutScope.MotionProperties> motionProperties(String id);
+    method @Deprecated public androidx.constraintlayout.compose.MotionLayoutScope.MotionProperties motionProperties(String id, String tag);
+  }
+
+  @androidx.constraintlayout.compose.ExperimentalMotionApi public final class MotionLayoutScope.CustomProperties {
+    method public long color(String name);
+    method public float distance(String name);
+    method public float float(String name);
+    method public long fontSize(String name);
+    method public int int(String name);
   }
 
   @androidx.constraintlayout.compose.ExperimentalMotionApi public final class MotionLayoutScope.MotionProperties {
@@ -654,6 +696,7 @@
     method public void addTransition(androidx.constraintlayout.compose.Transition transition, optional String? name);
     method public androidx.constraintlayout.compose.ConstraintSetRef constraintSet(optional String? name, optional androidx.constraintlayout.compose.ConstraintSetRef? extendConstraintSet, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstraintSetScope,kotlin.Unit> constraintSetContent);
     method public androidx.constraintlayout.compose.ConstrainedLayoutReference createRefFor(Object id);
+    method public androidx.constraintlayout.compose.MotionSceneScope.ConstrainedLayoutReferences createRefsFor(java.lang.Object... ids);
     method public void customColor(androidx.constraintlayout.compose.ConstrainScope, String name, long value);
     method public void customColor(androidx.constraintlayout.compose.KeyAttributeScope, String name, long value);
     method public void customDistance(androidx.constraintlayout.compose.ConstrainScope, String name, float value);
@@ -668,6 +711,25 @@
     method public void transition(androidx.constraintlayout.compose.ConstraintSetRef from, androidx.constraintlayout.compose.ConstraintSetRef to, optional String? name, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.TransitionScope,kotlin.Unit> transitionContent);
   }
 
+  public final class MotionSceneScope.ConstrainedLayoutReferences {
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component1();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component10();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component11();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component12();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component13();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component14();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component15();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component16();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component2();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component3();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component4();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component5();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component6();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component7();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component8();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component9();
+  }
+
   public final class MotionSceneScopeKt {
     method @androidx.constraintlayout.compose.ExperimentalMotionApi public static androidx.constraintlayout.compose.MotionScene MotionScene(kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionSceneScope,kotlin.Unit> motionSceneContent);
   }
diff --git a/constraintlayout/constraintlayout-compose/api/restricted_current.txt b/constraintlayout/constraintlayout-compose/api/restricted_current.txt
index b18e252..d7d773c 100644
--- a/constraintlayout/constraintlayout-compose/api/restricted_current.txt
+++ b/constraintlayout/constraintlayout-compose/api/restricted_current.txt
@@ -3,6 +3,7 @@
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface BaselineAnchorable {
     method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.BaselineAnchor anchor, optional float margin, optional float goneMargin);
+    method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor anchor, optional float margin, optional float goneMargin);
   }
 
   @androidx.compose.runtime.Immutable public final class ChainStyle {
@@ -19,6 +20,13 @@
     property public final androidx.constraintlayout.compose.ChainStyle SpreadInside;
   }
 
+  @kotlin.PublishedApi internal enum CompositionSource {
+    method public static androidx.constraintlayout.compose.CompositionSource valueOf(String name) throws java.lang.IllegalArgumentException;
+    method public static androidx.constraintlayout.compose.CompositionSource[] values();
+    enum_constant public static final androidx.constraintlayout.compose.CompositionSource Content;
+    enum_constant public static final androidx.constraintlayout.compose.CompositionSource Unknown;
+  }
+
   @androidx.compose.foundation.layout.LayoutScopeMarker @androidx.compose.runtime.Stable public final class ConstrainScope {
     method public void centerAround(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor anchor);
     method public void centerAround(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor anchor);
@@ -32,6 +40,7 @@
     method public androidx.constraintlayout.compose.VerticalAnchorable getAbsoluteLeft();
     method public androidx.constraintlayout.compose.VerticalAnchorable getAbsoluteRight();
     method public float getAlpha();
+    method public androidx.constraintlayout.compose.Dimension getAsDimension(float);
     method public androidx.constraintlayout.compose.BaselineAnchorable getBaseline();
     method public androidx.constraintlayout.compose.HorizontalAnchorable getBottom();
     method public androidx.constraintlayout.compose.VerticalAnchorable getEnd();
@@ -132,6 +141,7 @@
     method public final androidx.constraintlayout.compose.HorizontalChainScope constrain(androidx.constraintlayout.compose.HorizontalChainReference ref, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.HorizontalChainScope,kotlin.Unit> constrainBlock);
     method public final androidx.constraintlayout.compose.VerticalChainScope constrain(androidx.constraintlayout.compose.VerticalChainReference ref, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.VerticalChainScope,kotlin.Unit> constrainBlock);
     method public final androidx.constraintlayout.compose.ConstrainScope constrain(androidx.constraintlayout.compose.ConstrainedLayoutReference ref, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstrainScope,kotlin.Unit> constrainBlock);
+    method public final void constrain(androidx.constraintlayout.compose.ConstrainedLayoutReference![] refs, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.ConstrainScope,kotlin.Unit> constrainBlock);
     method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createAbsoluteLeftBarrier(androidx.constraintlayout.compose.LayoutReference![] elements, optional float margin);
     method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.VerticalAnchor createAbsoluteRightBarrier(androidx.constraintlayout.compose.LayoutReference![] elements, optional float margin);
     method public final androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor createBottomBarrier(androidx.constraintlayout.compose.LayoutReference![] elements, optional float margin);
@@ -202,8 +212,6 @@
     method public static androidx.constraintlayout.compose.Dimension getAtLeastWrapContent(androidx.constraintlayout.compose.Dimension.MinCoercible);
     method public static androidx.constraintlayout.compose.Dimension.MinCoercible getAtMostWrapContent(androidx.constraintlayout.compose.Dimension.Coercible);
     method public static androidx.constraintlayout.compose.Dimension getAtMostWrapContent(androidx.constraintlayout.compose.Dimension.MaxCoercible);
-    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static kotlin.Pair<androidx.compose.ui.layout.MeasurePolicy,kotlin.jvm.functions.Function0<kotlin.Unit>> rememberConstraintLayoutMeasurePolicy(int optimizationLevel, androidx.constraintlayout.compose.ConstraintLayoutScope scope, androidx.compose.runtime.MutableState<java.lang.Boolean> remeasureRequesterState, androidx.constraintlayout.compose.Measurer measurer);
-    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static androidx.compose.ui.layout.MeasurePolicy rememberConstraintLayoutMeasurePolicy(int optimizationLevel, androidx.compose.runtime.MutableState<java.lang.Long> needsUpdate, androidx.constraintlayout.compose.ConstraintSet constraintSet, androidx.constraintlayout.compose.Measurer measurer);
   }
 
   @androidx.compose.foundation.layout.LayoutScopeMarker public final class ConstraintLayoutScope extends androidx.constraintlayout.compose.ConstraintLayoutBaseScope {
@@ -252,12 +260,45 @@
     method public default androidx.constraintlayout.compose.ConstraintSet override(String name, float value);
   }
 
+  @kotlin.PublishedApi internal final class ConstraintSetForInlineDsl implements androidx.constraintlayout.compose.ConstraintSet androidx.compose.runtime.RememberObserver {
+    ctor public ConstraintSetForInlineDsl(androidx.constraintlayout.compose.ConstraintLayoutScope scope);
+    method public void applyTo(androidx.constraintlayout.compose.State state, java.util.List<? extends androidx.compose.ui.layout.Measurable> measurables);
+    method public boolean getKnownDirty();
+    method public androidx.constraintlayout.compose.ConstraintLayoutScope getScope();
+    method public void onAbandoned();
+    method public void onForgotten();
+    method public void onRemembered();
+    method public void setKnownDirty(boolean);
+    property public final boolean knownDirty;
+    property public final androidx.constraintlayout.compose.ConstraintLayoutScope scope;
+  }
+
   public final class ConstraintSetRef {
     method public androidx.constraintlayout.compose.ConstraintSetRef copy(String name);
   }
 
   @androidx.compose.foundation.layout.LayoutScopeMarker public final class ConstraintSetScope extends androidx.constraintlayout.compose.ConstraintLayoutBaseScope {
     method public androidx.constraintlayout.compose.ConstrainedLayoutReference createRefFor(Object id);
+    method public androidx.constraintlayout.compose.ConstraintSetScope.ConstrainedLayoutReferences createRefsFor(java.lang.Object... ids);
+  }
+
+  public final class ConstraintSetScope.ConstrainedLayoutReferences {
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component1();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component10();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component11();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component12();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component13();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component14();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component15();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component16();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component2();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component3();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component4();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component5();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component6();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component7();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component8();
+    method public operator androidx.constraintlayout.compose.ConstrainedLayoutReference component9();
   }
 
   public final class DesignElements {
@@ -349,6 +390,7 @@
 
   @kotlin.jvm.JvmDefaultWithCompatibility public interface HorizontalAnchorable {
     method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.HorizontalAnchor anchor, optional float margin, optional float goneMargin);
+    method public void linkTo(androidx.constraintlayout.compose.ConstraintLayoutBaseScope.BaselineAnchor anchor, optional float margin, optional float goneMargin);
   }
 
   @androidx.compose.runtime.Stable public final class HorizontalChainReference extends androidx.constraintlayout.compose.LayoutReference {
@@ -461,7 +503,7 @@
   }
 
   public final class MotionDragHandlerKt {
-    method @kotlin.PublishedApi internal static inline androidx.compose.ui.Modifier motionPointerInput(androidx.compose.ui.Modifier, optional Object key, androidx.constraintlayout.compose.MotionProgress motionProgress, androidx.constraintlayout.compose.MotionMeasurer measurer);
+    method @kotlin.PublishedApi internal static inline androidx.compose.ui.Modifier motionPointerInput(androidx.compose.ui.Modifier, Object key, androidx.constraintlayout.compose.MotionProgress motionProgress, androidx.constraintlayout.compose.MotionMeasurer measurer);
   }
 
   @kotlin.PublishedApi internal final class MotionDragState {
@@ -516,12 +558,13 @@
 
   public final class MotionLayoutKt {
     method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static inline void MotionLayoutCore(androidx.constraintlayout.compose.MotionScene motionScene, optional androidx.compose.ui.Modifier modifier, optional String? constraintSetName, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, optional androidx.constraintlayout.compose.MotionLayoutDebugFlags debugFlag, optional int optimizationLevel, optional java.util.Set<? extends androidx.constraintlayout.compose.MotionLayoutFlag> motionLayoutFlags, optional kotlin.jvm.functions.Function0<kotlin.Unit>? finishedAnimationListener, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static inline void MotionLayoutCore(androidx.constraintlayout.compose.MotionScene motionScene, float progress, optional androidx.compose.ui.Modifier modifier, optional java.util.EnumSet<androidx.constraintlayout.compose.MotionLayoutDebugFlags> debug, optional int optimizationLevel, String transitionName, optional java.util.Set<? extends androidx.constraintlayout.compose.MotionLayoutFlag> motionLayoutFlags, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static inline void MotionLayoutCore(androidx.constraintlayout.compose.ConstraintSet start, androidx.constraintlayout.compose.ConstraintSet end, optional androidx.compose.ui.Modifier modifier, optional androidx.constraintlayout.compose.TransitionImpl? transition, androidx.constraintlayout.compose.MotionProgress motionProgress, optional androidx.constraintlayout.compose.MotionLayoutDebugFlags debugFlag, optional androidx.constraintlayout.compose.LayoutInformationReceiver? informationReceiver, optional int optimizationLevel, optional java.util.Set<? extends androidx.constraintlayout.compose.MotionLayoutFlag> motionLayoutFlags, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static inline void MotionLayoutCore(optional androidx.compose.ui.Modifier modifier, optional int optimizationLevel, androidx.constraintlayout.compose.MotionLayoutStateImpl motionLayoutState, androidx.constraintlayout.compose.MotionScene motionScene, optional String transitionName, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static inline void MotionLayoutCore(androidx.constraintlayout.compose.MotionScene motionScene, float progress, String transitionName, int optimizationLevel, java.util.Set<? extends androidx.constraintlayout.compose.MotionLayoutFlag> motionLayoutFlags, java.util.EnumSet<androidx.constraintlayout.compose.MotionLayoutDebugFlags> debug, androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static inline void MotionLayoutCore(androidx.constraintlayout.compose.MotionScene motionScene, String transitionName, androidx.constraintlayout.compose.MotionLayoutStateImpl motionLayoutState, int optimizationLevel, androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static inline void MotionLayoutCore(androidx.constraintlayout.compose.ConstraintSet start, androidx.constraintlayout.compose.ConstraintSet end, androidx.constraintlayout.compose.TransitionImpl? transition, androidx.constraintlayout.compose.MotionProgress motionProgress, androidx.constraintlayout.compose.LayoutInformationReceiver? informationReceiver, int optimizationLevel, boolean showBounds, boolean showPaths, boolean showKeyPositions, androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.constraintlayout.compose.MotionLayoutScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static void UpdateWithForcedIfNoUserChange(androidx.constraintlayout.compose.MotionProgress motionProgress, androidx.constraintlayout.compose.LayoutInformationReceiver? informationReceiver);
-    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static inline androidx.constraintlayout.compose.MotionProgress createAndUpdateMotionProgress(float progress);
-    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static androidx.compose.ui.layout.MeasurePolicy rememberMotionLayoutMeasurePolicy(int optimizationLevel, java.util.EnumSet<androidx.constraintlayout.compose.MotionLayoutDebugFlags> debug, androidx.constraintlayout.compose.ConstraintSet constraintSetStart, androidx.constraintlayout.compose.ConstraintSet constraintSetEnd, androidx.constraintlayout.compose.TransitionImpl? transition, androidx.constraintlayout.compose.MotionProgress motionProgress, optional java.util.Set<? extends androidx.constraintlayout.compose.MotionLayoutFlag> motionLayoutFlags, androidx.constraintlayout.compose.MotionMeasurer measurer);
+    method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static androidx.constraintlayout.compose.MotionProgress createAndUpdateMotionProgress(float progress);
+    method @kotlin.PublishedApi internal static androidx.compose.ui.Modifier motionDebug(androidx.compose.ui.Modifier, androidx.constraintlayout.compose.MotionMeasurer measurer, float scaleFactor, boolean showBounds, boolean showPaths, boolean showKeyPositions);
+    method @kotlin.PublishedApi internal static androidx.compose.ui.layout.MeasurePolicy motionLayoutMeasurePolicy(androidx.compose.runtime.State<kotlin.Unit> contentTracker, androidx.compose.ui.node.Ref<androidx.constraintlayout.compose.CompositionSource> compositionSource, androidx.constraintlayout.compose.ConstraintSet constraintSetStart, androidx.constraintlayout.compose.ConstraintSet constraintSetEnd, androidx.constraintlayout.compose.TransitionImpl transition, androidx.constraintlayout.compose.MotionProgress motionProgress, androidx.constraintlayout.compose.MotionMeasurer measurer, int optimizationLevel);
   }
 
   @androidx.compose.runtime.Immutable @kotlin.PublishedApi internal final class MotionLayoutStateImpl {
@@ -543,13 +586,13 @@
   @kotlin.PublishedApi internal final class MotionMeasurer extends androidx.constraintlayout.compose.Measurer {
     ctor public MotionMeasurer(androidx.compose.ui.unit.Density density);
     method public void clearConstraintSets();
-    method public void drawDebug(androidx.compose.ui.graphics.drawscope.DrawScope);
+    method public void drawDebug(androidx.compose.ui.graphics.drawscope.DrawScope, optional boolean drawBounds, optional boolean drawPaths, optional boolean drawKeyPositions);
     method public void encodeRoot(StringBuilder json);
     method public long getCustomColor(String id, String name, float progress);
     method public float getCustomFloat(String id, String name, float progress);
     method public androidx.constraintlayout.core.state.Transition getTransition();
-    method public void initWith(androidx.constraintlayout.compose.ConstraintSet start, androidx.constraintlayout.compose.ConstraintSet end, androidx.compose.ui.unit.Density density, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.constraintlayout.compose.TransitionImpl? transition, float progress);
-    method public long performInterpolationMeasure(long constraints, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.constraintlayout.compose.ConstraintSet constraintSetStart, androidx.constraintlayout.compose.ConstraintSet constraintSetEnd, androidx.constraintlayout.compose.TransitionImpl? transition, java.util.List<? extends androidx.compose.ui.layout.Measurable> measurables, int optimizationLevel, float progress, optional java.util.Set<? extends androidx.constraintlayout.compose.MotionLayoutFlag> motionLayoutFlags);
+    method public void initWith(androidx.constraintlayout.compose.ConstraintSet start, androidx.constraintlayout.compose.ConstraintSet end, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.constraintlayout.compose.TransitionImpl transition, float progress);
+    method public long performInterpolationMeasure(long constraints, androidx.compose.ui.unit.LayoutDirection layoutDirection, androidx.constraintlayout.compose.ConstraintSet constraintSetStart, androidx.constraintlayout.compose.ConstraintSet constraintSetEnd, androidx.constraintlayout.compose.TransitionImpl transition, java.util.List<? extends androidx.compose.ui.layout.Measurable> measurables, int optimizationLevel, float progress, androidx.constraintlayout.compose.CompositionSource compositionSource);
     property public final androidx.constraintlayout.core.state.Transition transition;
   }
 
@@ -597,6 +640,18 @@
     method public suspend Object? updateProgressWhileTouchUp(kotlin.coroutines.Continuation<? super kotlin.Unit>);
   }
 
+  @kotlin.PublishedApi internal final class TransitionImpl {
+    ctor public TransitionImpl(androidx.constraintlayout.core.parser.CLObject parsedTransition);
+    method public void applyAllTo(androidx.constraintlayout.core.state.Transition transition);
+    method public void applyKeyFramesTo(androidx.constraintlayout.core.state.Transition transition);
+    method public String getEndConstraintSetId();
+    method public String getStartConstraintSetId();
+    field @kotlin.PublishedApi internal static final androidx.constraintlayout.compose.TransitionImpl EMPTY;
+  }
+
+  @kotlin.PublishedApi internal static final class TransitionImpl.Companion {
+  }
+
   public final class TransitionKt {
   }
 
diff --git a/constraintlayout/constraintlayout-compose/build.gradle b/constraintlayout/constraintlayout-compose/build.gradle
index 2d46d36..bc8b31b 100644
--- a/constraintlayout/constraintlayout-compose/build.gradle
+++ b/constraintlayout/constraintlayout-compose/build.gradle
@@ -46,6 +46,8 @@
         androidTestImplementation(libs.testRules)
         androidTestImplementation(libs.testRunner)
         androidTestImplementation(libs.junit)
+
+        lintPublish(project(":constraintlayout:constraintlayout-compose-lint"))
     }
 }
 
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/constraintlayout-compose-demos/src/main/java/androidx/constraintlayout/compose/demos/AllDemos.kt b/constraintlayout/constraintlayout-compose/integration-tests/constraintlayout-compose-demos/src/main/java/androidx/constraintlayout/compose/demos/AllDemos.kt
new file mode 100644
index 0000000..20dacf6
--- /dev/null
+++ b/constraintlayout/constraintlayout-compose/integration-tests/constraintlayout-compose-demos/src/main/java/androidx/constraintlayout/compose/demos/AllDemos.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.constraintlayout.compose.demos
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Icon
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+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.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+data class ComposeDemo(val title: String, val content: @Composable () -> Unit)
+
+val AllComposeConstraintLayoutDemos: List<ComposeDemo> =
+    listOf(
+        ComposeDemo("CustomColorInKeyAttributes") { CustomColorInKeyAttributesDemo() },
+        ComposeDemo("SimpleOnSwipe") { SimpleOnSwipe() },
+        ComposeDemo("AnimatedChainOrientation") { ChainsAnimatedOrientationDemo() }
+    )
+
+/**
+ * Main screen to explore and interact with all demos from [AllComposeConstraintLayoutDemos].
+ */
+@Preview
+@Composable
+fun ComposeConstraintLayoutDemos() {
+    var displayedDemo by remember { mutableStateOf<ComposeDemo?>(null) }
+    Column {
+        Column {
+            displayedDemo?.let {
+                // Header with back button
+                Row(
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .height(50.dp)
+                        .background(Color.White)
+                        .graphicsLayer(shadowElevation = 2f)
+                        .clickable { displayedDemo = null }, // Return to list of demos
+                    verticalAlignment = Alignment.CenterVertically
+                ) {
+                    Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back")
+                    Text(text = it.title)
+                }
+            } ?: kotlin.run {
+                // Main Title
+                Text(text = "ComposeConstraintLayoutDemos", style = MaterialTheme.typography.h6)
+                Spacer(modifier = Modifier.height(8.dp))
+            }
+        }
+        Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
+            displayedDemo?.let { demo ->
+                // Display selected demo
+                Box(
+                    Modifier
+                        .fillMaxWidth()
+                        .weight(1.0f, true)
+                ) {
+                    demo.content()
+                }
+            } ?: kotlin.run {
+                // Display list of demos
+                AllComposeConstraintLayoutDemos.forEach {
+                    ComposeDemoItem(it.title) { displayedDemo = it }
+                }
+            }
+        }
+    }
+}
+
+@Composable
+private fun ComposeDemoItem(title: String, modifier: Modifier = Modifier, onClick: () -> Unit) {
+    Box(
+        modifier = modifier
+            .padding(horizontal = 8.dp)
+            .fillMaxWidth()
+            .height(44.dp)
+            .clip(RoundedCornerShape(10.dp))
+            .background(Color.White)
+            .clickable(onClick = onClick)
+            .padding(start = 8.dp),
+        contentAlignment = Alignment.CenterStart
+    ) {
+        Text(
+            text = title,
+            modifier = Modifier,
+            fontSize = 16.sp
+        )
+    }
+}
\ No newline at end of file
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/constraintlayout-compose-demos/src/main/java/androidx/constraintlayout/compose/demos/ChainsDemo.kt b/constraintlayout/constraintlayout-compose/integration-tests/constraintlayout-compose-demos/src/main/java/androidx/constraintlayout/compose/demos/ChainsDemo.kt
new file mode 100644
index 0000000..5611bc4
--- /dev/null
+++ b/constraintlayout/constraintlayout-compose/integration-tests/constraintlayout-compose-demos/src/main/java/androidx/constraintlayout/compose/demos/ChainsDemo.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.constraintlayout.compose.demos
+
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material.Button
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.constraintlayout.compose.ConstraintLayout
+import androidx.constraintlayout.compose.ConstraintSet
+import androidx.constraintlayout.compose.Dimension
+
+@Preview
+@Composable
+fun ChainsAnimatedOrientationDemo() {
+    val boxColors = listOf(Color.Red, Color.Blue, Color.Green)
+    var isHorizontal by remember { mutableStateOf(true) }
+
+    Column(Modifier.fillMaxSize()) {
+        ConstraintLayout(
+            constraintSet = ConstraintSet {
+                val (box0, box1, box2) = createRefsFor("box0", "box1", "box2")
+                box1.withChainParams(8.dp, 8.dp, 8.dp, 8.dp)
+
+                if (isHorizontal) {
+                    constrain(box0, box1, box2) {
+                        width = Dimension.fillToConstraints
+                        height = Dimension.value(20.dp)
+                        centerVerticallyTo(parent)
+                    }
+                    constrain(box1) {
+                        // Override height to be a ratio
+                        height = Dimension.ratio("2:1")
+                    }
+
+                    createHorizontalChain(box0, box1, box2)
+                } else {
+                    constrain(box0, box1, box2) {
+                        width = Dimension.value(20.dp)
+                        height = Dimension.fillToConstraints
+                        centerHorizontallyTo(parent)
+                    }
+                    constrain(box1) {
+                        // Override width to be a ratio
+                        width = Dimension.ratio("2:1")
+                    }
+
+                    createVerticalChain(box0, box1, box2)
+                }
+            },
+            animateChanges = true,
+            animationSpec = tween(800),
+            modifier = Modifier
+                .fillMaxWidth()
+                .weight(1.0f, true)
+        ) {
+            boxColors.forEachIndexed { index, color ->
+                Box(
+                    modifier = Modifier
+                        .layoutId("box$index")
+                        .background(color)
+                )
+            }
+        }
+        Button(onClick = { isHorizontal = !isHorizontal }) {
+            Text(text = "Toggle Orientation")
+        }
+    }
+}
\ No newline at end of file
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/constraintlayout-compose-demos/src/main/java/androidx/constraintlayout/compose/demos/CustomKeyAttributesDemo.kt b/constraintlayout/constraintlayout-compose/integration-tests/constraintlayout-compose-demos/src/main/java/androidx/constraintlayout/compose/demos/CustomKeyAttributesDemo.kt
index fc0af6f..d660ec7 100644
--- a/constraintlayout/constraintlayout-compose/integration-tests/constraintlayout-compose-demos/src/main/java/androidx/constraintlayout/compose/demos/CustomKeyAttributesDemo.kt
+++ b/constraintlayout/constraintlayout-compose/integration-tests/constraintlayout-compose-demos/src/main/java/androidx/constraintlayout/compose/demos/CustomKeyAttributesDemo.kt
@@ -32,7 +32,6 @@
 import androidx.constraintlayout.compose.Dimension
 import androidx.constraintlayout.compose.ExperimentalMotionApi
 import androidx.constraintlayout.compose.MotionLayout
-import androidx.constraintlayout.compose.MotionLayoutFlag
 import androidx.constraintlayout.compose.MotionScene
 import androidx.constraintlayout.compose.OnSwipe
 import androidx.constraintlayout.compose.SwipeDirection
@@ -95,11 +94,14 @@
             }
         },
         progress = 0f,
-        motionLayoutFlags = setOf(MotionLayoutFlag.FullMeasure), // Needed to update text composable
         modifier = Modifier.fillMaxSize()
     ) {
-        val background = motionColor(boxId, "background")
-        Box(modifier = Modifier.layoutId(boxId).background(background))
+        val background = customColor(boxId, "background")
+        Box(
+            modifier = Modifier
+                .layoutId(boxId)
+                .background(background)
+        )
         Text(
             modifier = Modifier.layoutId(textId),
             text = "Color: ${background.toArgb().toUInt().toString(16)}"
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/constraintlayout-compose-demos/src/main/java/androidx/constraintlayout/compose/demos/OnSwipeDemos.kt b/constraintlayout/constraintlayout-compose/integration-tests/constraintlayout-compose-demos/src/main/java/androidx/constraintlayout/compose/demos/OnSwipeDemos.kt
index 59b84d1..6a9c88f 100644
--- a/constraintlayout/constraintlayout-compose/integration-tests/constraintlayout-compose-demos/src/main/java/androidx/constraintlayout/compose/demos/OnSwipeDemos.kt
+++ b/constraintlayout/constraintlayout-compose/integration-tests/constraintlayout-compose-demos/src/main/java/androidx/constraintlayout/compose/demos/OnSwipeDemos.kt
@@ -145,7 +145,7 @@
         ) {
             Box(
                 modifier = Modifier
-                    .background(motionProperties(id = "box").value.color("bColor"))
+                    .background(customProperties(id = "box").color("bColor"))
                     .layoutId("box")
             )
         }
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark-target/src/main/java/androidx/constraintlayout/compose/integration/macrobenchmark/target/newmessage/NewMessage.kt b/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark-target/src/main/java/androidx/constraintlayout/compose/integration/macrobenchmark/target/newmessage/NewMessage.kt
index 8f99fe2..8d144a9 100644
--- a/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark-target/src/main/java/androidx/constraintlayout/compose/integration/macrobenchmark/target/newmessage/NewMessage.kt
+++ b/constraintlayout/constraintlayout-compose/integration-tests/macrobenchmark-target/src/main/java/androidx/constraintlayout/compose/integration/macrobenchmark/target/newmessage/NewMessage.kt
@@ -485,7 +485,7 @@
     }
     Surface(
         modifier = Modifier.layoutId("box"),
-        color = motionColor(id = "box", name = "background"),
+        color = customColor(id = "box", name = "background"),
         elevation = 4.dp,
         shape = RoundedCornerShape(8.dp)
     ) {}
@@ -495,7 +495,7 @@
             NewMessageLayout.Fab -> Icons.Default.Edit
             else -> Icons.Default.Close
         },
-        color = motionColor("editClose", "content"),
+        color = customColor("editClose", "content"),
         enabled = true
     ) {
         when (currentState) {
@@ -506,7 +506,7 @@
     ColorableIconButton(
         modifier = Modifier.layoutId("minIcon"),
         imageVector = Icons.Default.KeyboardArrowDown,
-        color = motionColor("minIcon", "content"),
+        color = customColor("minIcon", "content"),
         enabled = true
     ) {
         when (currentState) {
@@ -517,7 +517,7 @@
     CheapText(
         text = dialogName,
         modifier = Modifier.layoutId("title"),
-        color = motionColor("title", "content"),
+        color = customColor("title", "content"),
         style = MaterialTheme.typography.h6
     )
     MessageWidget(modifier = Modifier.layoutId("content"), onDelete = {
diff --git a/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/ConstraintLayoutTest.kt b/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/ConstraintLayoutTest.kt
index 1153106..4f49804 100644
--- a/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/ConstraintLayoutTest.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/ConstraintLayoutTest.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.IntrinsicSize
 import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -26,6 +27,7 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -34,7 +36,9 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.FirstBaseline
 import androidx.compose.ui.layout.boundsInParent
+import androidx.compose.ui.layout.layout
 import androidx.compose.ui.layout.layoutId
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.layout.positionInParent
@@ -46,6 +50,7 @@
 import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.unit.IntOffset
@@ -1928,6 +1933,70 @@
     }
 
     @Test
+    fun testConstraintSet_multipleRefs() = with(rule.density) {
+        val rootSize = 50
+        val boxSize = 20
+        val margin = 10
+        val positions = Array(3) { IntOffset.Zero }
+        rule.setContent {
+            ConstraintLayout(
+                constraintSet = ConstraintSet {
+                    // Note that not enough IDs were provided, box2 will have a generated ID
+                    val (box0, box1, box2) = createRefsFor("box0", "box1")
+
+                    constrain(box0, box1) {
+                        width = boxSize.toDp().asDimension
+                        height = boxSize.toDp().asDimension
+                        top.linkTo(parent.top, margin.toDp())
+                        start.linkTo(parent.start, margin.toDp())
+                    }
+                    constrain(box2) {
+                        width = boxSize.toDp().asDimension
+                        height = boxSize.toDp().asDimension
+
+                        top.linkTo(box0.bottom)
+                        start.linkTo(box0.end)
+                    }
+                },
+                Modifier.size(rootSize.toDp())
+            ) {
+                Box(
+                    Modifier
+                        .layoutId("box0")
+                        .onGloballyPositioned {
+                            positions[0] = it
+                                .positionInRoot()
+                                .round()
+                        }
+                )
+                Box(
+                    Modifier
+                        .layoutId("box1")
+                        .onGloballyPositioned {
+                            positions[1] = it
+                                .positionInRoot()
+                                .round()
+                        }
+                )
+                Box(
+                    Modifier
+                        // Generated id, tho normally, the user wouldn't know what the ID is
+                        .layoutId("androidx.constraintlayout.id0")
+                        .onGloballyPositioned {
+                            positions[2] = it
+                                .positionInRoot()
+                                .round()
+                        }
+                )
+            }
+        }
+        rule.waitForIdle()
+        assertEquals(IntOffset(margin, margin), positions[0])
+        assertEquals(IntOffset(margin, margin), positions[1])
+        assertEquals(IntOffset(margin + boxSize, margin + boxSize), positions[2])
+    }
+
+    @Test
     fun testLinkToBias_withInlineDsl_rtl() = with(rule.density) {
         val rootSize = 200
         val boxSize = 20
@@ -1997,6 +2066,309 @@
         }
     }
 
+    @Test
+    fun testContentRecomposition_withConstraintSet() = with(rule.density) {
+        var constraintLayoutCompCount = 0
+
+        val baseWidth = 10
+        val box0WidthMultiplier = mutableStateOf(2)
+        val boxHeight = 30
+        rule.setContent {
+            ++constraintLayoutCompCount
+            ConstraintLayout(
+                constraintSet = ConstraintSet {
+                    val (box0, box1) = createRefsFor("box0", "box1")
+                    constrain(box0) {
+                        // previously, preferredWrapContent would fail if only the content
+                        // recomposed
+                        width = Dimension.preferredWrapContent
+
+                        start.linkTo(parent.start)
+                        end.linkTo(parent.end)
+                        horizontalBias = 0f
+
+                        top.linkTo(parent.top)
+                    }
+                    constrain(box1) {
+                        width = Dimension.fillToConstraints
+                        height = Dimension.wrapContent
+                        start.linkTo(box0.start)
+                        end.linkTo(box0.end)
+                        horizontalBias = 0f
+
+                        top.linkTo(box0.bottom)
+                    }
+                }
+            ) {
+                Box(
+                    Modifier
+                        .height(boxHeight.toDp())
+                        .width((baseWidth * box0WidthMultiplier.value).toDp())
+                        .layoutTestId("box0")
+                        .background(Color.Red)
+                )
+                Box(
+                    Modifier
+                        .height(boxHeight.toDp())
+                        .layoutTestId("box1")
+                        .background(Color.Blue)
+                )
+            }
+        }
+        rule.waitForIdle()
+
+        rule.onNodeWithTag("box0").apply {
+            assertPositionInRootIsEqualTo(0.dp, 0.dp)
+            assertWidthIsEqualTo(20.toDp()) // (box0WidthMultiplier.value * baseWidth).toDp()
+        }
+        rule.onNodeWithTag("box1").apply {
+            assertPositionInRootIsEqualTo(0.dp, boxHeight.toDp())
+            assertWidthIsEqualTo(20.toDp()) // (box0WidthMultiplier.value * baseWidth).toDp()
+        }
+
+        box0WidthMultiplier.value = 3
+        rule.waitForIdle()
+
+        rule.onNodeWithTag("box0").apply {
+            assertPositionInRootIsEqualTo(0.dp, 0.dp)
+            assertWidthIsEqualTo(30.toDp()) // (box0WidthMultiplier.value * baseWidth).toDp()
+        }
+        rule.onNodeWithTag("box1").apply {
+            assertPositionInRootIsEqualTo(0.dp, boxHeight.toDp())
+            assertWidthIsEqualTo(30.toDp()) // (box0WidthMultiplier.value * baseWidth).toDp()
+        }
+
+        box0WidthMultiplier.value = 1
+        rule.waitForIdle()
+
+        rule.onNodeWithTag("box0").apply {
+            assertPositionInRootIsEqualTo(0.dp, 0.dp)
+            assertWidthIsEqualTo(10.toDp()) // (box0WidthMultiplier.value * baseWidth).toDp()
+        }
+        rule.onNodeWithTag("box1").apply {
+            assertPositionInRootIsEqualTo(0.dp, boxHeight.toDp())
+            assertWidthIsEqualTo(10.toDp()) // (box0WidthMultiplier.value * baseWidth).toDp()
+        }
+
+        assertEquals(1, constraintLayoutCompCount)
+    }
+
+    @Test
+    fun testContentRecomposition_withInlineModifier() = with(rule.density) {
+        var constraintLayoutCompCount = 0
+
+        val baseWidth = 10
+        val box0WidthMultiplier = mutableStateOf(2)
+        val boxHeight = 30
+        rule.setContent {
+            ++constraintLayoutCompCount
+            ConstraintLayout {
+                val (box0, box1) = createRefs()
+                Box(
+                    Modifier
+                        .height(boxHeight.toDp())
+                        .width((baseWidth * box0WidthMultiplier.value).toDp())
+                        .constrainAs(box0) {
+                            // previously, preferredWrapContent would fail if only the content
+                            // recomposed
+                            width = Dimension.preferredWrapContent
+
+                            start.linkTo(parent.start)
+                            end.linkTo(parent.end)
+                            horizontalBias = 0f
+
+                            top.linkTo(parent.top)
+                        }
+                        .testTag("box0")
+                        .background(Color.Red)
+                )
+                Box(
+                    Modifier
+                        .height(boxHeight.toDp())
+                        .constrainAs(box1) {
+                            width = Dimension.fillToConstraints
+                            height = Dimension.wrapContent
+                            start.linkTo(box0.start)
+                            end.linkTo(box0.end)
+                            horizontalBias = 0f
+
+                            top.linkTo(box0.bottom)
+                        }
+                        .testTag("box1")
+                        .background(Color.Blue)
+                )
+            }
+        }
+        rule.waitForIdle()
+
+        rule.onNodeWithTag("box0").apply {
+            assertPositionInRootIsEqualTo(0.dp, 0.dp)
+            assertWidthIsEqualTo(20.toDp()) // (box0WidthMultiplier.value * baseWidth).toDp()
+        }
+        rule.onNodeWithTag("box1").apply {
+            assertPositionInRootIsEqualTo(0.dp, boxHeight.toDp())
+            assertWidthIsEqualTo(20.toDp()) // (box0WidthMultiplier.value * baseWidth).toDp()
+        }
+
+        box0WidthMultiplier.value = 3
+        rule.waitForIdle()
+
+        rule.onNodeWithTag("box0").apply {
+            assertPositionInRootIsEqualTo(0.dp, 0.dp)
+            assertWidthIsEqualTo(30.toDp()) // (box0WidthMultiplier.value * baseWidth).toDp()
+        }
+        rule.onNodeWithTag("box1").apply {
+            assertPositionInRootIsEqualTo(0.dp, boxHeight.toDp())
+            assertWidthIsEqualTo(30.toDp()) // (box0WidthMultiplier.value * baseWidth).toDp()
+        }
+
+        box0WidthMultiplier.value = 1
+        rule.waitForIdle()
+
+        rule.onNodeWithTag("box0").apply {
+            assertPositionInRootIsEqualTo(0.dp, 0.dp)
+            assertWidthIsEqualTo(10.toDp()) // (box0WidthMultiplier.value * baseWidth).toDp()
+        }
+        rule.onNodeWithTag("box1").apply {
+            assertPositionInRootIsEqualTo(0.dp, boxHeight.toDp())
+            assertWidthIsEqualTo(10.toDp()) // (box0WidthMultiplier.value * baseWidth).toDp()
+        }
+
+        assertEquals(1, constraintLayoutCompCount)
+    }
+
+    @Test
+    fun testBaselineConstraints() = with(rule.density) {
+        fun Modifier.withBaseline() = this.layout { measurable, constraints ->
+            val placeable = measurable.measure(constraints)
+            val halfHeight = (placeable.height / 2f).roundToInt()
+            layout(
+                width = placeable.width,
+                height = placeable.height,
+                alignmentLines = mapOf(FirstBaseline to halfHeight)
+            ) {
+                placeable.place(0, 0)
+            }
+        }
+
+        val boxSize = 10
+        val box1Margin = 13
+        val box2Margin = -7f
+
+        var box1Position = IntOffset.Zero
+        var box2Position = IntOffset.Zero
+        rule.setContent {
+            ConstraintLayout {
+                val (box1, box2) = createRefs()
+                Box(
+                    Modifier
+                        .size(boxSize.toDp())
+                        .withBaseline()
+                        .constrainAs(box1) {
+                            baseline.linkTo(parent.top, box1Margin.toDp())
+                            start.linkTo(parent.start)
+                        }
+                        .onGloballyPositioned {
+                            box1Position = it
+                                .positionInRoot()
+                                .round()
+                        })
+                Box(
+                    Modifier
+                        .size(boxSize.toDp())
+                        .withBaseline()
+                        .constrainAs(box2) {
+                            top.linkTo(box1.baseline, box2Margin.toDp())
+                        }
+                        .onGloballyPositioned {
+                            box2Position = it
+                                .positionInRoot()
+                                .round()
+                        })
+            }
+        }
+        val expectedBox1Y = box1Margin - (boxSize * 0.5f).roundToInt()
+        val expectedBox2Y = expectedBox1Y + box2Margin + (boxSize * 0.5f).toInt()
+        rule.runOnIdle {
+            assertEquals(IntOffset(0, expectedBox1Y), box1Position)
+            assertEquals(IntOffset(0, expectedBox2Y.roundToInt()), box2Position)
+        }
+    }
+
+    @Test
+    fun testConstraintLayout_withParentIntrinsics() = with(rule.density) {
+        val rootBoxWidth = 200
+        val box1Size = 40
+        val box2Size = 70
+
+        var rootSize = IntSize.Zero
+        var clSize = IntSize.Zero
+        var box1Position = IntOffset.Zero
+        var box2Position = IntOffset.Zero
+
+        rule.setContent {
+            Box(
+                modifier = Modifier
+                    .width(rootBoxWidth.toDp())
+                    .height(IntrinsicSize.Max)
+                    .background(Color.LightGray)
+                    .onGloballyPositioned {
+                        rootSize = it.size
+                    }
+            ) {
+                ConstraintLayout(
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .wrapContentHeight()
+                        .background(Color.Yellow)
+                        .onGloballyPositioned {
+                            clSize = it.size
+                        }
+                ) {
+                    val (one, two) = createRefs()
+                    val horChain =
+                        createHorizontalChain(one, two, chainStyle = ChainStyle.Packed(0f))
+                    constrain(horChain) {
+                        start.linkTo(parent.start)
+                        end.linkTo(parent.end)
+                    }
+                    Box(
+                        Modifier
+                            .size(box1Size.toDp())
+                            .background(Color.Green)
+                            .constrainAs(one) {
+                                top.linkTo(parent.top)
+                                bottom.linkTo(parent.bottom)
+                            }
+                            .onGloballyPositioned {
+                                box1Position = it.positionInRoot().round()
+                            })
+                    Box(
+                        Modifier
+                            .size(box2Size.toDp())
+                            .background(Color.Red)
+                            .constrainAs(two) {
+                                width = Dimension.preferredWrapContent
+                                top.linkTo(parent.top)
+                                bottom.linkTo(parent.bottom)
+                            }
+                            .onGloballyPositioned {
+                                box2Position = it.positionInRoot().round()
+                            })
+                }
+            }
+        }
+
+        val expectedSize = IntSize(rootBoxWidth, box2Size)
+        val expectedBox1Y = ((box2Size / 2f) - (box1Size / 2f)).roundToInt()
+        rule.runOnIdle {
+            assertEquals(expectedSize, rootSize)
+            assertEquals(expectedSize, clSize)
+            assertEquals(IntOffset(0, expectedBox1Y), box1Position)
+            assertEquals(IntOffset(box1Size, 0), box2Position)
+        }
+    }
+
     private fun listAnchors(box: ConstrainedLayoutReference): List<ConstrainScope.() -> Unit> {
         // TODO(172055763) directly construct an immutable list when Lint supports it
         val anchors = mutableListOf<ConstrainScope.() -> Unit>()
diff --git a/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/MotionLayoutTest.kt b/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/MotionLayoutTest.kt
index 980a920..1709620 100644
--- a/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/MotionLayoutTest.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/MotionLayoutTest.kt
@@ -22,7 +22,12 @@
 import androidx.compose.foundation.border
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.material.LocalTextStyle
 import androidx.compose.material.Text
@@ -38,6 +43,8 @@
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.positionInRoot
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.test.getUnclippedBoundsInRoot
@@ -48,12 +55,16 @@
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.round
 import androidx.compose.ui.unit.size
 import androidx.compose.ui.unit.sp
 import androidx.constraintlayout.compose.test.R
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
+import kotlin.math.roundToInt
 import kotlin.test.assertEquals
 import org.junit.Rule
 import org.junit.Test
@@ -67,7 +78,7 @@
     val rule = createComposeRule()
 
     /**
-     * Tests that [MotionLayoutScope.motionFontSize] works as expected.
+     * Tests that [MotionLayoutScope.customFontSize] works as expected.
      *
      * See custom_text_size_scene.json5
      */
@@ -138,19 +149,33 @@
                 progress = progress.value,
                 modifier = Modifier.size(200.dp)
             ) {
-                val props by motionProperties(id = "element")
+                val props = customProperties(id = "element")
                 Column(Modifier.layoutId("element")) {
                     Text(
-                        text = "Color: #${props.color("color").toArgb().toUInt().toString(16)}"
+                        text = "1) Color: #${props.color("color").toHexString()}"
                     )
                     Text(
-                        text = "Distance: ${props.distance("distance")}"
+                        text = "2) Distance: ${props.distance("distance")}"
                     )
                     Text(
-                        text = "FontSize: ${props.fontSize("fontSize")}"
+                        text = "3) FontSize: ${props.fontSize("fontSize")}"
                     )
                     Text(
-                        text = "Int: ${props.int("int")}"
+                        text = "4) Int: ${props.int("int")}"
+                    )
+
+                    // Missing properties
+                    Text(
+                        text = "5) Color: #${props.color("a").toHexString()}"
+                    )
+                    Text(
+                        text = "6) Distance: ${props.distance("b")}"
+                    )
+                    Text(
+                        text = "7) FontSize: ${props.fontSize("c")}"
+                    )
+                    Text(
+                        text = "8) Int: ${props.int("d")}"
                     )
                 }
             }
@@ -159,18 +184,114 @@
 
         progress.value = 0.25f
         rule.waitForIdle()
-        rule.onNodeWithText("Color: #ffffbaba").assertExists()
-        rule.onNodeWithText("Distance: 10.0.dp").assertExists()
-        rule.onNodeWithText("FontSize: 15.0.sp").assertExists()
-        rule.onNodeWithText("Int: 20").assertExists()
+        rule.onNodeWithText("1) Color: #ffffbaba").assertExists()
+        rule.onNodeWithText("2) Distance: 10.0.dp").assertExists()
+        rule.onNodeWithText("3) FontSize: 15.0.sp").assertExists()
+        rule.onNodeWithText("4) Int: 20").assertExists()
+
+        // Undefined custom properties
+        rule.onNodeWithText("5) Color: #0").assertExists()
+        rule.onNodeWithText("6) Distance: Dp.Unspecified").assertExists()
+        rule.onNodeWithText("7) FontSize: NaN.sp").assertExists()
+        rule.onNodeWithText("8) Int: 0").assertExists()
 
         progress.value = 0.75f
         rule.waitForIdle()
-        rule.onNodeWithText("Color: #ffba0000").assertExists()
-        rule.onNodeWithText("Distance: 15.0.dp").assertExists()
-        rule.onNodeWithText("FontSize: 25.0.sp").assertExists()
-        rule.onNodeWithText("Int: 35").assertExists()
+        rule.onNodeWithText("1) Color: #ffba0000").assertExists()
+        rule.onNodeWithText("2) Distance: 15.0.dp").assertExists()
+        rule.onNodeWithText("3) FontSize: 25.0.sp").assertExists()
+        rule.onNodeWithText("4) Int: 35").assertExists()
+
+        // Undefined custom properties
+        rule.onNodeWithText("5) Color: #0").assertExists()
+        rule.onNodeWithText("6) Distance: Dp.Unspecified").assertExists()
+        rule.onNodeWithText("7) FontSize: NaN.sp").assertExists()
+        rule.onNodeWithText("8) Int: 0").assertExists()
     }
+
+    @Test
+    fun testMotionLayout_withParentIntrinsics() = with(rule.density) {
+        val constraintSet = ConstraintSet {
+            val (one, two) = createRefsFor("one", "two")
+            val horChain = createHorizontalChain(one, two, chainStyle = ChainStyle.Packed(0f))
+            constrain(horChain) {
+                start.linkTo(parent.start)
+                end.linkTo(parent.end)
+            }
+            constrain(one) {
+                top.linkTo(parent.top)
+                bottom.linkTo(parent.bottom)
+            }
+            constrain(two) {
+                width = Dimension.preferredWrapContent
+                top.linkTo(parent.top)
+                bottom.linkTo(parent.bottom)
+            }
+        }
+
+        val rootBoxWidth = 200
+        val box1Size = 40
+        val box2Size = 70
+
+        var rootSize = IntSize.Zero
+        var mlSize = IntSize.Zero
+        var box1Position = IntOffset.Zero
+        var box2Position = IntOffset.Zero
+
+        rule.setContent {
+            Box(
+                modifier = Modifier
+                    .width(rootBoxWidth.toDp())
+                    .height(IntrinsicSize.Max)
+                    .background(Color.LightGray)
+                    .onGloballyPositioned {
+                        rootSize = it.size
+                    }
+            ) {
+                MotionLayout(
+                    start = constraintSet,
+                    end = constraintSet,
+                    transition = Transition {},
+                    progress = 0f, // We're not testing the animation
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .wrapContentHeight()
+                        .background(Color.Yellow)
+                        .onGloballyPositioned {
+                            mlSize = it.size
+                        }
+                ) {
+                    Box(
+                        Modifier
+                            .size(box1Size.toDp())
+                            .background(Color.Green)
+                            .layoutId("one")
+                            .onGloballyPositioned {
+                                box1Position = it.positionInRoot().round()
+                            })
+                    Box(
+                        Modifier
+                            .size(box2Size.toDp())
+                            .background(Color.Red)
+                            .layoutId("two")
+                            .onGloballyPositioned {
+                                box2Position = it.positionInRoot().round()
+                            })
+                }
+            }
+        }
+
+        val expectedSize = IntSize(rootBoxWidth, box2Size)
+        val expectedBox1Y = ((box2Size / 2f) - (box1Size / 2f)).roundToInt()
+        rule.runOnIdle {
+            assertEquals(expectedSize, rootSize)
+            assertEquals(expectedSize, mlSize)
+            assertEquals(IntOffset(0, expectedBox1Y), box1Position)
+            assertEquals(IntOffset(box1Size, 0), box2Position)
+        }
+    }
+
+    private fun Color.toHexString(): String = toArgb().toUInt().toString(16)
 }
 
 @OptIn(ExperimentalMotionApi::class)
@@ -195,7 +316,7 @@
             progress = progress,
             modifier = modifier
         ) {
-            val profilePicProperties = motionProperties(id = "profile_pic")
+            val profilePicProperties = customProperties(id = "profile_pic")
             Box(
                 modifier = Modifier
                     .layoutTestId("box")
@@ -208,16 +329,16 @@
                     .clip(CircleShape)
                     .border(
                         width = 2.dp,
-                        color = profilePicProperties.value.color("background"),
+                        color = profilePicProperties.color("background"),
                         shape = CircleShape
                     )
                     .layoutTestId("profile_pic")
             )
             Text(
                 text = "Hello",
-                fontSize = motionFontSize("username", "textSize"),
+                fontSize = customFontSize("username", "textSize"),
                 modifier = Modifier.layoutTestId("username"),
-                color = profilePicProperties.value.color("background")
+                color = profilePicProperties.color("background")
             )
         }
     }
diff --git a/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/MultiMeasureCompositionTest.kt b/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/MultiMeasureCompositionTest.kt
new file mode 100644
index 0000000..5f80af3
--- /dev/null
+++ b/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/MultiMeasureCompositionTest.kt
@@ -0,0 +1,261 @@
+/*
+ * 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.constraintlayout.compose
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.neverEqualPolicy
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.MeasurePolicy
+import androidx.compose.ui.layout.MultiMeasureLayout
+import androidx.compose.ui.node.Ref
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import kotlin.test.assertEquals
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val HEIGHT_FROM_CONTENT = 40
+private const val HEIGHT_FROM_CALLER = 80
+
+/**
+ * This class tests a couple of assumptions that ConstraintLayout & MotionLayout need to operate
+ * properly.
+ *
+ * See [MaxWrapContentWithMultiMeasure].
+ */
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class MultiMeasureCompositionTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun testCustomMultiMeasure_changesFromCompositionSource(): Unit = with(rule.density) {
+        var callerCompositionCount = 0
+        var contentCompositionCount = 0
+
+        val minWidth = 40
+
+        // Mutable state that is only read in the content, will not directly recompose our
+        // MultiMeasure Composable
+        val widthMultiplier = mutableStateOf(4)
+        val baseWidth = 10
+
+        // Mutable state that is read at the same scope of our MultiMeasure Composable, will cause
+        // it to recompose, but does not directly affect the content
+        val unusedValue = mutableStateOf(0)
+        rule.setContent {
+            Column(
+                Modifier
+                    .fillMaxSize()
+                    .background(Color.LightGray)
+            ) {
+                ++callerCompositionCount
+                unusedValue.value
+                MaxWrapContentWithMultiMeasure {
+                    ++contentCompositionCount
+                    // Box with variable width, depends on the multiplier value
+                    Box(
+                        modifier = Modifier
+                            .width((widthMultiplier.value * baseWidth).toDp())
+                            .background(Color.Red)
+                            .testTag("box0")
+                    )
+                    // Box with constant width
+                    Box(
+                        Modifier
+                            .width(minWidth.toDp())
+                            .background(Color.Blue)
+                            .testTag("box1")
+                    )
+                }
+            }
+        }
+        rule.waitForIdle()
+
+        // Assert the initial layout, composed from the root, so height is HEIGHT_FROM_CALLER
+        rule.onNodeWithTag("box0").apply {
+            assertPositionInRootIsEqualTo(0.dp, 0.dp)
+            assertWidthIsEqualTo(minWidth.toDp())
+            assertHeightIsEqualTo(HEIGHT_FROM_CALLER.toDp())
+        }
+        rule.onNodeWithTag("box1").apply {
+            assertPositionInRootIsEqualTo(0.dp, HEIGHT_FROM_CALLER.toDp())
+            assertWidthIsEqualTo(minWidth.toDp())
+            assertHeightIsEqualTo(HEIGHT_FROM_CALLER.toDp())
+        }
+
+        rule.runOnIdle {
+            // Increase multiplier, this will cause the layout to recompose from the content
+            widthMultiplier.value = widthMultiplier.value + 1
+        }
+        rule.waitForIdle()
+
+        // MaxWrapContentWithMultiMeasure assigns different height when recomposed from the content
+        rule.onNodeWithTag("box0").apply {
+            assertPositionInRootIsEqualTo(0.dp, 0.dp)
+            assertWidthIsEqualTo(50.toDp()) // baseWidth * widthMultiplier.value
+            assertHeightIsEqualTo(HEIGHT_FROM_CONTENT.toDp())
+        }
+        rule.onNodeWithTag("box1").apply {
+            assertPositionInRootIsEqualTo(0.dp, HEIGHT_FROM_CONTENT.toDp())
+            assertWidthIsEqualTo(50.toDp()) // baseWidth * widthMultiplier.value
+            assertHeightIsEqualTo(HEIGHT_FROM_CONTENT.toDp())
+        }
+
+        rule.runOnIdle {
+            // Decrease multiplier
+            widthMultiplier.value = 3
+        }
+        rule.waitForIdle()
+
+        // Verify layout is still correct
+        rule.onNodeWithTag("box0").apply {
+            assertPositionInRootIsEqualTo(0.dp, 0.dp)
+            assertWidthIsEqualTo(minWidth.toDp())
+            assertHeightIsEqualTo(HEIGHT_FROM_CONTENT.toDp())
+        }
+        rule.onNodeWithTag("box1").apply {
+            assertPositionInRootIsEqualTo(0.dp, HEIGHT_FROM_CONTENT.toDp())
+            assertWidthIsEqualTo(minWidth.toDp())
+            assertHeightIsEqualTo(HEIGHT_FROM_CONTENT.toDp())
+        }
+
+        rule.runOnIdle {
+            // This causes a recomposition from the caller of our Composable
+            unusedValue.value = 1
+        }
+        rule.waitForIdle()
+
+        // MaxWrapContentWithMultiMeasure assigns different height when recomposed from the Caller
+        rule.onNodeWithTag("box0").apply {
+            assertPositionInRootIsEqualTo(0.dp, 0.dp)
+            assertWidthIsEqualTo(minWidth.toDp())
+            assertHeightIsEqualTo(HEIGHT_FROM_CALLER.toDp())
+        }
+        rule.onNodeWithTag("box1").apply {
+            assertPositionInRootIsEqualTo(0.dp, HEIGHT_FROM_CALLER.toDp())
+            assertWidthIsEqualTo(minWidth.toDp())
+            assertHeightIsEqualTo(HEIGHT_FROM_CALLER.toDp())
+        }
+
+        rule.runOnIdle {
+            assertEquals(2, callerCompositionCount)
+            assertEquals(4, contentCompositionCount)
+        }
+    }
+
+    /**
+     * Column-like layout that assigns the max WrapContent width to all its children.
+     *
+     * Note that the height assigned height will depend on where the recomposition started: 40px if it
+     * started from the content, 80px if it started from the Composable caller.
+     */
+    @Composable
+    inline fun MaxWrapContentWithMultiMeasure(
+        modifier: Modifier = Modifier,
+        crossinline content: @Composable () -> Unit
+    ) {
+        val compTracker = remember { mutableStateOf(Unit, neverEqualPolicy()) }
+        val compSource =
+            remember { Ref<CompositionSource>().apply { value = CompositionSource.Unknown } }
+        compSource.value = CompositionSource.Caller
+
+        @Suppress("DEPRECATION")
+        MultiMeasureLayout(
+            modifier = modifier,
+            measurePolicy = maxWidthPolicy(compTracker, compSource),
+            content = {
+                // Reassign the mutable state, so that readers recompose with the content
+                compTracker.value = Unit
+
+                if (compSource.value == CompositionSource.Unknown) {
+                    compSource.value = CompositionSource.Content
+                }
+                content()
+            }
+        )
+    }
+
+    fun maxWidthPolicy(
+        compTracker: State<Unit>,
+        compSource: Ref<CompositionSource>
+    ): MeasurePolicy =
+        MeasurePolicy { measurables, constraints ->
+            // This state read will force the MeasurePolicy to re-run whenever the content
+            // recomposes, even if our Composable didn't
+            compTracker.value
+
+            val height = when (compSource.value) {
+                CompositionSource.Content -> HEIGHT_FROM_CONTENT
+                CompositionSource.Caller -> HEIGHT_FROM_CALLER
+                CompositionSource.Unknown,
+                null -> 0
+            }
+
+            compSource.value = CompositionSource.Unknown
+
+            // Find the max WrapContent width
+            val maxWrapWidth = measurables.map {
+                it.measure(constraints.copy(minWidth = 0, minHeight = height))
+            }.maxOf {
+                it.width
+            }
+
+            // Remeasure, assign the maxWrapWidth to every child
+            val placeables = measurables.map {
+                it.measure(constraints.copy(minWidth = maxWrapWidth, minHeight = height))
+            }
+
+            // Wrap the layout height to the content in a column
+            var layoutHeight = 0
+            placeables.forEach { layoutHeight += it.height }
+
+            // Position the children.
+            layout(maxWrapWidth, layoutHeight) {
+                var y = 0
+                placeables.forEach { placeable ->
+                    // Position left-aligned, one after another
+                    placeable.place(x = 0, y = y)
+                    y += placeable.height
+                }
+            }
+        }
+
+    enum class CompositionSource {
+        Unknown,
+        Caller,
+        Content
+    }
+}
\ No newline at end of file
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstrainScope.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstrainScope.kt
index eed1540..cd5d8c4 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstrainScope.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstrainScope.kt
@@ -449,6 +449,14 @@
         containerObject.remove("pivotY")
     }
 
+    /**
+     * Convenience extension variable to parse a [Dp] as a [Dimension] object.
+     *
+     * @see Dimension.value
+     */
+    val Dp.asDimension: Dimension
+        get() = Dimension.value(this)
+
     private inner class DimensionProperty(initialValue: Dimension) :
         ObservableProperty<Dimension>(initialValue) {
         override fun afterChange(property: KProperty<*>, oldValue: Dimension, newValue: Dimension) {
@@ -520,4 +528,22 @@
         }
         containerObject.put("baseline", constraintArray)
     }
+
+    /**
+     * Adds a link towards a [ConstraintLayoutBaseScope.HorizontalAnchor].
+     */
+    override fun linkTo(
+        anchor: ConstraintLayoutBaseScope.HorizontalAnchor,
+        margin: Dp,
+        goneMargin: Dp
+    ) {
+        val targetAnchorName = AnchorFunctions.horizontalAnchorIndexToAnchorName(anchor.index)
+        val constraintArray = CLArray(charArrayOf()).apply {
+            add(CLString.from(anchor.id.toString()))
+            add(CLString.from(targetAnchorName))
+            add(CLNumber(margin.value))
+            add(CLNumber(goneMargin.value))
+        }
+        containerObject.put("baseline", constraintArray)
+    }
 }
\ No newline at end of file
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintLayout.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintLayout.kt
index b6d7025..06e001d 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintLayout.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintLayout.kt
@@ -41,6 +41,7 @@
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.neverEqualPolicy
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshots.SnapshotStateObserver
@@ -120,17 +121,42 @@
     val measurer = remember { Measurer(density) }
     val scope = remember { ConstraintLayoutScope() }
     val remeasureRequesterState = remember { mutableStateOf(false) }
-    val (measurePolicy, onHelpersChanged) = rememberConstraintLayoutMeasurePolicy(
-        optimizationLevel,
-        scope,
-        remeasureRequesterState,
-        measurer
-    )
+    val constraintSet = remember { ConstraintSetForInlineDsl(scope) }
+    val contentTracker = remember { mutableStateOf(Unit, neverEqualPolicy()) }
+
+    val measurePolicy = MeasurePolicy { measurables, constraints ->
+        contentTracker.value
+        val layoutSize = measurer.performMeasure(
+            constraints,
+            layoutDirection,
+            constraintSet,
+            measurables,
+            optimizationLevel
+        )
+        // We read the remeasurement requester state, to request remeasure when the value
+        // changes. This will happen when the scope helpers are changing at recomposition.
+        remeasureRequesterState.value
+
+        layout(layoutSize.width, layoutSize.height) {
+            with(measurer) { performLayout(measurables) }
+        }
+    }
+
+    val onHelpersChanged = {
+        // If the helpers have changed, we need to request remeasurement. To achieve this,
+        // we are changing this boolean state that is read during measurement.
+        remeasureRequesterState.value = !remeasureRequesterState.value
+        constraintSet.knownDirty = true
+    }
+
     @Suppress("Deprecation")
     MultiMeasureLayout(
         modifier = modifier.semantics { designInfoProvider = measurer },
         measurePolicy = measurePolicy,
         content = {
+            // Perform a reassignment to the State tracker, this will force readers to recompose at
+            // the same pass as the content. The only expected reader is our MeasurePolicy.
+            contentTracker.value = Unit
             val previousHelpersHashCode = scope.helpersHashCode
             scope.reset()
             scope.content()
@@ -144,46 +170,8 @@
     )
 }
 
-@Composable
 @PublishedApi
-internal fun rememberConstraintLayoutMeasurePolicy(
-    optimizationLevel: Int,
-    scope: ConstraintLayoutScope,
-    remeasureRequesterState: MutableState<Boolean>,
-    measurer: Measurer
-): Pair<MeasurePolicy, () -> Unit> {
-    val constraintSet = remember { ConstraintSetForInlineDsl(scope) }
-
-    return remember(optimizationLevel) {
-        val measurePolicy = MeasurePolicy { measurables, constraints ->
-            val layoutSize = measurer.performMeasure(
-                constraints,
-                layoutDirection,
-                constraintSet,
-                measurables,
-                optimizationLevel
-            )
-            // We read the remeasurement requester state, to request remeasure when the value
-            // changes. This will happen when the scope helpers are changing at recomposition.
-            remeasureRequesterState.value
-
-            layout(layoutSize.width, layoutSize.height) {
-                with(measurer) { performLayout(measurables) }
-            }
-        }
-
-        val onHelpersChanged = {
-            // If the helpers have changed, we need to request remeasurement. To achieve this,
-            // we are changing this boolean state that is read during measurement.
-            remeasureRequesterState.value = !remeasureRequesterState.value
-            constraintSet.knownDirty = true
-        }
-
-        measurePolicy to onHelpersChanged
-    }
-}
-
-private class ConstraintSetForInlineDsl(
+internal class ConstraintSetForInlineDsl(
     val scope: ConstraintLayoutScope
 ) : ConstraintSet, RememberObserver {
     private var handler: Handler? = null
@@ -263,7 +251,7 @@
     animateChanges: Boolean = false,
     animationSpec: AnimationSpec<Float> = tween<Float>(),
     noinline finishedAnimationListener: (() -> Unit)? = null,
-    noinline content: @Composable () -> Unit
+    crossinline content: @Composable () -> Unit
 ) {
     if (animateChanges) {
         var startConstraint by remember { mutableStateOf(constraintSet) }
@@ -304,14 +292,26 @@
             mutableStateOf(0L)
         }
 
+        val contentTracker = remember { mutableStateOf(Unit, neverEqualPolicy()) }
         val density = LocalDensity.current
         val measurer = remember { Measurer(density) }
-        val measurePolicy = rememberConstraintLayoutMeasurePolicy(
-            optimizationLevel,
-            needsUpdate,
-            constraintSet,
-            measurer
-        )
+        remember(constraintSet) {
+            measurer.parseDesignElements(constraintSet)
+            true
+        }
+        val measurePolicy = MeasurePolicy { measurables, constraints ->
+            contentTracker.value
+            val layoutSize = measurer.performMeasure(
+                constraints,
+                layoutDirection,
+                constraintSet,
+                measurables,
+                optimizationLevel
+            )
+            layout(layoutSize.width, layoutSize.height) {
+                with(measurer) { performLayout(measurables) }
+            }
+        }
         if (constraintSet is EditableJSONLayout) {
             constraintSet.setUpdateFlag(needsUpdate)
         }
@@ -340,6 +340,10 @@
                 modifier = modifier.semantics { designInfoProvider = measurer },
                 measurePolicy = measurePolicy,
                 content = {
+                    // Perform a reassignment to the State tracker, this will force readers to
+                    // recompose at the same pass as the content. The only expected reader is our
+                    // MeasurePolicy.
+                    contentTracker.value = Unit
                     measurer.createDesignElements()
                     content()
                 }
@@ -348,29 +352,6 @@
     }
 }
 
-@Composable
-@PublishedApi
-internal fun rememberConstraintLayoutMeasurePolicy(
-    optimizationLevel: Int,
-    needsUpdate: MutableState<Long>,
-    constraintSet: ConstraintSet,
-    measurer: Measurer
-) = remember(optimizationLevel, needsUpdate.value, constraintSet) {
-    measurer.parseDesignElements(constraintSet)
-    MeasurePolicy { measurables, constraints ->
-        val layoutSize = measurer.performMeasure(
-            constraints,
-            layoutDirection,
-            constraintSet,
-            measurables,
-            optimizationLevel
-        )
-        layout(layoutSize.width, layoutSize.height) {
-            with(measurer) { performLayout(measurables) }
-        }
-    }
-}
-
 /**
  * Scope used by the inline DSL of [ConstraintLayout].
  */
@@ -462,11 +443,90 @@
 @LayoutScopeMarker
 class ConstraintSetScope internal constructor(extendFrom: CLObject?) :
     ConstraintLayoutBaseScope(extendFrom) {
+    private var generatedCount = 0
+
+    /**
+     * Generate an ID to be used as fallback if the user didn't provide enough parameters to
+     * [createRefsFor].
+     *
+     * Not intended to be used, but helps prevent runtime issues.
+     */
+    private fun nextId() = "androidx.constraintlayout.id" + generatedCount++
+
     /**
      * Creates one [ConstrainedLayoutReference] corresponding to the [ConstraintLayout] element
      * with [id].
      */
     fun createRefFor(id: Any): ConstrainedLayoutReference = ConstrainedLayoutReference(id)
+
+    /**
+     * Convenient way to create multiple [ConstrainedLayoutReference] with one statement, the [ids]
+     * provided should match Composables within ConstraintLayout using [Modifier.layoutId].
+     *
+     * Example:
+     * ```
+     * val (box, text, button) = createRefsFor("box", "text", "button")
+     * ```
+     * Note that the number of ids should match the number of variables assigned.
+     *
+     * &nbsp;
+     *
+     * To create a singular [ConstrainedLayoutReference] see [createRefFor].
+     */
+    fun createRefsFor(vararg ids: Any): ConstrainedLayoutReferences =
+        ConstrainedLayoutReferences(arrayOf(*ids))
+
+    inner class ConstrainedLayoutReferences internal constructor(
+        private val ids: Array<Any>
+    ) {
+        operator fun component1(): ConstrainedLayoutReference =
+            ConstrainedLayoutReference(ids.getOrElse(0) { nextId() })
+
+        operator fun component2(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(1) { nextId() })
+
+        operator fun component3(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(2) { nextId() })
+
+        operator fun component4(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(3) { nextId() })
+
+        operator fun component5(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(4) { nextId() })
+
+        operator fun component6(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(5) { nextId() })
+
+        operator fun component7(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(6) { nextId() })
+
+        operator fun component8(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(7) { nextId() })
+
+        operator fun component9(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(8) { nextId() })
+
+        operator fun component10(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(9) { nextId() })
+
+        operator fun component11(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(10) { nextId() })
+
+        operator fun component12(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(11) { nextId() })
+
+        operator fun component13(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(12) { nextId() })
+
+        operator fun component14(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(13) { nextId() })
+
+        operator fun component15(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(14) { nextId() })
+
+        operator fun component16(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(15) { nextId() })
+    }
 }
 
 /**
@@ -1298,6 +1358,8 @@
                 SolverDimension.createWrap().min(constraints.minHeight)
             }
         )
+        state.mParent.width.apply(state, root, ConstraintWidget.HORIZONTAL)
+        state.mParent.height.apply(state, root, ConstraintWidget.VERTICAL)
         // Build constraint set and apply it to the state.
         state.rootIncomingConstraints = constraints
         state.isLtr = layoutDirection == LayoutDirection.Ltr
@@ -1310,6 +1372,7 @@
         } else {
             buildMapping(state, measurables)
         }
+
         applyRootSize(constraints)
         root.updateHierarchy()
 
@@ -1330,24 +1393,6 @@
         root.optimizationLevel = optimizationLevel
         root.measure(root.optimizationLevel, 0, 0, 0, 0, 0, 0, 0, 0)
 
-        for (child in root.children) {
-            val measurable = child.companionWidget
-            if (measurable !is Measurable) continue
-            val placeable = placeables[measurable]
-            val currentWidth = placeable?.width
-            val currentHeight = placeable?.height
-            if (child.width != currentWidth || child.height != currentHeight) {
-                if (DEBUG) {
-                    Log.d(
-                        "CCL",
-                        "Final measurement for ${measurable.layoutId} " +
-                            "to confirm size ${child.width} ${child.height}"
-                    )
-                }
-                measurable.measure(Constraints.fixed(child.width, child.height))
-                    .also { placeables[measurable] = it }
-            }
-        }
         if (DEBUG) {
             Log.d("CCL", "ConstraintLayout is at the end ${root.width} ${root.height}")
         }
@@ -1675,6 +1720,11 @@
  * [ConstraintLayoutParentData.ref] or [ConstraintLayoutTagParentData.constraintLayoutId].
  *
  * The Tag is set from [ConstraintLayoutTagParentData.constraintLayoutTag].
+ *
+ * This should always be performed for every Measure call, since there's no guarantee that the
+ * [Measurable]s will be the same instance, even if there's seemingly no changes.
+ * Should be called before applying the [State] or, if there's no need to apply it, should be called
+ * before measuring.
  */
 internal fun buildMapping(state: State, measurables: List<Measurable>) {
     measurables.fastForEach { measurable ->
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintLayoutBaseScope.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintLayoutBaseScope.kt
index 994d2ea..2e74d93 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintLayoutBaseScope.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintLayoutBaseScope.kt
@@ -131,6 +131,18 @@
     ): ConstrainScope = ConstrainScope(ref.id, ref.asCLContainer()).apply(constrainBlock)
 
     /**
+     * Convenient way to apply the same constraints to multiple [ConstrainedLayoutReference]s.
+     */
+    fun constrain(
+        vararg refs: ConstrainedLayoutReference,
+        constrainBlock: ConstrainScope.() -> Unit
+    ) {
+        refs.forEach { ref ->
+            constrain(ref, constrainBlock)
+        }
+    }
+
+    /**
      * Creates a guideline at a specific offset from the start of the [ConstraintLayout].
      */
     fun createGuidelineFromStart(offset: Dp): VerticalAnchor {
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintScopeCommon.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintScopeCommon.kt
index 5e8c2b3..11c8a8e 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintScopeCommon.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintScopeCommon.kt
@@ -55,6 +55,15 @@
         margin: Dp = 0.dp,
         goneMargin: Dp = 0.dp
     )
+
+    /**
+     * Adds a link towards a [ConstraintLayoutBaseScope.BaselineAnchor].
+     */
+    fun linkTo(
+        anchor: ConstraintLayoutBaseScope.BaselineAnchor,
+        margin: Dp = 0.dp,
+        goneMargin: Dp = 0.dp
+    )
 }
 
 @JvmDefaultWithCompatibility
@@ -71,6 +80,15 @@
         margin: Dp = 0.dp,
         goneMargin: Dp = 0.dp
     )
+
+    /**
+     * Adds a link towards a [ConstraintLayoutBaseScope.HorizontalAnchor].
+     */
+    fun linkTo(
+        anchor: ConstraintLayoutBaseScope.HorizontalAnchor,
+        margin: Dp = 0.dp,
+        goneMargin: Dp = 0.dp
+    )
 }
 
 internal abstract class BaseVerticalAnchorable(
@@ -115,6 +133,20 @@
         }
         containerObject.put(anchorName, constraintArray)
     }
+
+    final override fun linkTo(
+        anchor: ConstraintLayoutBaseScope.BaselineAnchor,
+        margin: Dp,
+        goneMargin: Dp
+    ) {
+        val constraintArray = CLArray(charArrayOf()).apply {
+            add(CLString.from(anchor.id.toString()))
+            add(CLString.from("baseline"))
+            add(CLNumber(margin.value))
+            add(CLNumber(goneMargin.value))
+        }
+        containerObject.put(anchorName, constraintArray)
+    }
 }
 
 internal object AnchorFunctions {
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintSet.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintSet.kt
index 5a3c088..0b97860 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintSet.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintSet.kt
@@ -51,7 +51,6 @@
     val extendFrom: ConstraintSet?
 
     override fun applyTo(state: State, measurables: List<Measurable>) {
-        buildMapping(state, measurables)
         extendFrom?.applyTo(state, measurables)
         applyToState(state)
     }
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionCarousel.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionCarousel.kt
index a439827..ba68ca1 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionCarousel.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionCarousel.kt
@@ -260,6 +260,7 @@
             ItemHolder(i, slotPrefix, showSlots) {
                 if (visible) {
                     if (provider.value.hasItemsWithProperties()) {
+                        @Suppress("DEPRECATION")
                         val properties = motionProperties("$slotPrefix$i")
                         provider.value.getContent(idx, properties).invoke()
                     } else {
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionDragHandler.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionDragHandler.kt
index 72f65ca..5f2cccd 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionDragHandler.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionDragHandler.kt
@@ -42,7 +42,7 @@
 @PublishedApi
 @ExperimentalMotionApi
 internal inline fun Modifier.motionPointerInput(
-    key: Any = Unit,
+    key: Any,
     motionProgress: MotionProgress,
     measurer: MotionMeasurer
 ): Modifier = composed(
@@ -67,10 +67,8 @@
             if (isTouchUp && swipeHandler.pendingProgressWhileTouchUp()) {
                 // Loop until there's no need to update the progress or the there's a touch down
                 swipeHandler.updateProgressWhileTouchUp()
-                // TODO: Once the progress while Up ends, snap the progress to target (0 or 1)
             } else {
                 if (dragState == null) {
-                    // TODO: Investigate if it's worth skipping some drag events
                     dragState = dragChannel.receive()
                 }
                 coroutineContext.ensureActive()
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionLayout.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionLayout.kt
index e3f663bc..c228b64 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionLayout.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionLayout.kt
@@ -26,6 +26,7 @@
 import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.neverEqualPolicy
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
@@ -56,12 +57,18 @@
     FullMeasure(1)
 }
 
+enum class MotionLayoutDebugFlags {
+    NONE,
+    SHOW_ALL,
+    UNKNOWN
+}
+
 /**
  * Layout that interpolate its children layout given two sets of constraint and
  * a progress (from 0 to 1)
  */
+@Suppress("UNUSED_PARAMETER")
 @ExperimentalMotionApi
-@Suppress("NOTHING_TO_INLINE")
 @Composable
 inline fun MotionLayout(
     start: ConstraintSet,
@@ -75,16 +82,18 @@
     crossinline content: @Composable MotionLayoutScope.() -> Unit
 ) {
     val motionProgress = createAndUpdateMotionProgress(progress = progress)
+    val showDebug = debug.firstOrNull() == MotionLayoutDebugFlags.SHOW_ALL
     MotionLayoutCore(
         start = start,
         end = end,
         transition = transition as? TransitionImpl,
         motionProgress = motionProgress,
-        debugFlag = debug.firstOrNull() ?: MotionLayoutDebugFlags.NONE,
         informationReceiver = null,
-        modifier = modifier,
         optimizationLevel = optimizationLevel,
-        motionLayoutFlags = motionLayoutFlags,
+        showBounds = showDebug,
+        showPaths = showDebug,
+        showKeyPositions = showDebug,
+        modifier = modifier,
         content = content
     )
 }
@@ -94,7 +103,6 @@
  * 1).
  */
 @ExperimentalMotionApi
-@Suppress("NOTHING_TO_INLINE")
 @Composable
 inline fun MotionLayout(
     motionScene: MotionScene,
@@ -156,7 +164,7 @@
     )
 }
 
-@Suppress("NOTHING_TO_INLINE")
+@Suppress("UNUSED_PARAMETER")
 @ExperimentalMotionApi
 @Composable
 inline fun MotionLayout(
@@ -172,16 +180,18 @@
     crossinline content: @Composable (MotionLayoutScope.() -> Unit)
 ) {
     val motionProgress = createAndUpdateMotionProgress(progress = progress)
+    val showDebug = debug.firstOrNull() == MotionLayoutDebugFlags.SHOW_ALL
     MotionLayoutCore(
         start = start,
         end = end,
         transition = transition as? TransitionImpl,
         motionProgress = motionProgress,
-        debugFlag = debug.firstOrNull() ?: MotionLayoutDebugFlags.NONE,
         informationReceiver = informationReceiver,
-        modifier = modifier,
         optimizationLevel = optimizationLevel,
-        motionLayoutFlags = motionLayoutFlags,
+        showBounds = showDebug,
+        showPaths = showDebug,
+        showKeyPositions = showDebug,
+        modifier = modifier,
         content = content
     )
 }
@@ -189,7 +199,7 @@
 @ExperimentalMotionApi
 @PublishedApi
 @Composable
-@Suppress("UnavailableSymbol")
+@Suppress("UnavailableSymbol", "UNUSED_PARAMETER")
 internal inline fun MotionLayoutCore(
     @Suppress("HiddenTypeParameter")
     motionScene: MotionScene,
@@ -276,11 +286,12 @@
         end = end,
         transition = transition as? TransitionImpl,
         motionProgress = motionProgress,
-        debugFlag = debugFlag,
         informationReceiver = motionScene as? LayoutInformationReceiver,
-        modifier = modifier,
         optimizationLevel = optimizationLevel,
-        motionLayoutFlags = motionLayoutFlags,
+        showBounds = debugFlag == MotionLayoutDebugFlags.SHOW_ALL,
+        showPaths = debugFlag == MotionLayoutDebugFlags.SHOW_ALL,
+        showKeyPositions = debugFlag == MotionLayoutDebugFlags.SHOW_ALL,
+        modifier = modifier,
         content = content
     )
 }
@@ -293,11 +304,11 @@
     @Suppress("HiddenTypeParameter")
     motionScene: MotionScene,
     progress: Float,
-    modifier: Modifier = Modifier,
-    debug: EnumSet<MotionLayoutDebugFlags> = EnumSet.of(MotionLayoutDebugFlags.NONE),
-    optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
     transitionName: String,
-    motionLayoutFlags: Set<MotionLayoutFlag> = setOf<MotionLayoutFlag>(),
+    optimizationLevel: Int,
+    motionLayoutFlags: Set<MotionLayoutFlag>,
+    debug: EnumSet<MotionLayoutDebugFlags>,
+    modifier: Modifier,
     @Suppress("HiddenTypeParameter")
     crossinline content: @Composable MotionLayoutScope.() -> Unit,
 ) {
@@ -332,91 +343,12 @@
 }
 
 @ExperimentalMotionApi
-@PublishedApi
-@Composable
-@Suppress("UnavailableSymbol")
-internal inline fun MotionLayoutCore(
-    start: ConstraintSet,
-    end: ConstraintSet,
-    modifier: Modifier = Modifier,
-    @SuppressWarnings("HiddenTypeParameter") transition: TransitionImpl? = null,
-    motionProgress: MotionProgress,
-    debugFlag: MotionLayoutDebugFlags = MotionLayoutDebugFlags.NONE,
-    informationReceiver: LayoutInformationReceiver? = null,
-    optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
-    motionLayoutFlags: Set<MotionLayoutFlag> = setOf<MotionLayoutFlag>(),
-    @Suppress("HiddenTypeParameter")
-    crossinline content: @Composable MotionLayoutScope.() -> Unit
-) {
-    // TODO: Merge this snippet with UpdateWithForcedIfNoUserChange
-    val needsUpdate = remember { mutableStateOf(0L) }
-    needsUpdate.value // Read the value to allow recomposition from informationReceiver
-    informationReceiver?.setUpdateFlag(needsUpdate)
-
-    UpdateWithForcedIfNoUserChange(
-        motionProgress = motionProgress,
-        informationReceiver = informationReceiver
-    )
-
-    var usedDebugMode = debugFlag
-    val forcedDebug = informationReceiver?.getForcedDrawDebug()
-    if (forcedDebug != null && forcedDebug != MotionLayoutDebugFlags.UNKNOWN) {
-        usedDebugMode = forcedDebug
-    }
-    val density = LocalDensity.current
-    val measurer = remember { MotionMeasurer(density) }
-    val scope = remember { MotionLayoutScope(measurer, motionProgress) }
-    val debug = EnumSet.of(usedDebugMode)
-    val measurePolicy =
-        rememberMotionLayoutMeasurePolicy(
-            optimizationLevel,
-            debug,
-            start,
-            end,
-            transition,
-            motionProgress,
-            motionLayoutFlags,
-            measurer
-        )
-
-    measurer.addLayoutInformationReceiver(informationReceiver)
-
-    val forcedScaleFactor = measurer.forcedScaleFactor
-
-    var debugModifications: Modifier = Modifier
-    if (!debug.contains(MotionLayoutDebugFlags.NONE) || !forcedScaleFactor.isNaN()) {
-        if (!forcedScaleFactor.isNaN()) {
-            debugModifications = debugModifications.scale(forcedScaleFactor)
-        }
-        debugModifications = debugModifications.drawBehind {
-            with(measurer) {
-                if (!forcedScaleFactor.isNaN()) {
-                    drawDebugBounds(forcedScaleFactor)
-                }
-                if (!debug.contains(MotionLayoutDebugFlags.NONE)) {
-                    drawDebug()
-                }
-            }
-        }
-    }
-    @Suppress("DEPRECATION")
-    (MultiMeasureLayout(
-        modifier = modifier
-            .then(debugModifications)
-            .motionPointerInput(measurePolicy, motionProgress, measurer)
-            .semantics { designInfoProvider = measurer },
-        measurePolicy = measurePolicy,
-        content = { scope.content() }
-    ))
-}
-
-@ExperimentalMotionApi
 @Composable
 inline fun MotionLayout(
+    motionScene: MotionScene,
+    motionLayoutState: MotionLayoutState,
     modifier: Modifier = Modifier,
     optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
-    motionLayoutState: MotionLayoutState,
-    motionScene: MotionScene,
     crossinline content: @Composable MotionLayoutScope.() -> Unit
 ) {
     MotionLayoutCore(
@@ -424,6 +356,7 @@
         optimizationLevel = optimizationLevel,
         motionLayoutState = motionLayoutState as MotionLayoutStateImpl,
         motionScene = motionScene,
+        transitionName = "default",
         content = content
     )
 }
@@ -433,12 +366,12 @@
 @Composable
 @Suppress("UnavailableSymbol")
 internal inline fun MotionLayoutCore(
-    modifier: Modifier = Modifier,
-    optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
-    motionLayoutState: MotionLayoutStateImpl,
     @Suppress("HiddenTypeParameter")
     motionScene: MotionScene,
-    transitionName: String = "default",
+    transitionName: String,
+    motionLayoutState: MotionLayoutStateImpl,
+    optimizationLevel: Int,
+    modifier: Modifier,
     @Suppress("HiddenTypeParameter")
     crossinline content: @Composable MotionLayoutScope.() -> Unit
 ) {
@@ -459,28 +392,190 @@
     if (start == null || end == null) {
         return
     }
+    val showDebug = motionLayoutState.debugMode == MotionLayoutDebugFlags.SHOW_ALL
     MotionLayoutCore(
         start = start,
         end = end,
         transition = transition as? TransitionImpl,
         motionProgress = motionLayoutState.motionProgress,
-        debugFlag = motionLayoutState.debugMode,
         informationReceiver = motionScene as? JSONMotionScene,
-        modifier = modifier,
         optimizationLevel = optimizationLevel,
+        showBounds = showDebug,
+        showPaths = showDebug,
+        showKeyPositions = showDebug,
+        modifier = modifier,
         content = content
     )
 }
 
+@ExperimentalMotionApi
+@PublishedApi
+@Composable
+@Suppress("UnavailableSymbol")
+internal inline fun MotionLayoutCore(
+    start: ConstraintSet,
+    end: ConstraintSet,
+    @SuppressWarnings("HiddenTypeParameter") transition: TransitionImpl?,
+    motionProgress: MotionProgress,
+    informationReceiver: LayoutInformationReceiver?,
+    optimizationLevel: Int,
+    showBounds: Boolean,
+    showPaths: Boolean,
+    showKeyPositions: Boolean,
+    modifier: Modifier,
+    @Suppress("HiddenTypeParameter")
+    crossinline content: @Composable MotionLayoutScope.() -> Unit
+) {
+    // TODO: Merge this snippet with UpdateWithForcedIfNoUserChange
+    val needsUpdate = remember { mutableStateOf(0L) }
+    needsUpdate.value // Read the value to allow recomposition from informationReceiver
+    informationReceiver?.setUpdateFlag(needsUpdate)
+
+    UpdateWithForcedIfNoUserChange(
+        motionProgress = motionProgress,
+        informationReceiver = informationReceiver
+    )
+
+    /**
+     * MutableState used to track content recompositions. It's reassigned at the content's
+     * composition scope, so that any function reading it is recomposed with the content.
+     * NeverEqualPolicy is used so that we don't have to assign any particular value to trigger a
+     * State change.
+     */
+    val contentTracker = remember { mutableStateOf(Unit, neverEqualPolicy()) }
+    val compositionSource =
+        remember { Ref<CompositionSource>().apply { value = CompositionSource.Unknown } }
+    val density = LocalDensity.current
+    val layoutDirection = LocalLayoutDirection.current
+    val measurer = remember { MotionMeasurer(density) }
+    val scope = remember { MotionLayoutScope(measurer, motionProgress) }
+
+    remember(start, end, transition) {
+        measurer.initWith(
+            start = start,
+            end = end,
+            layoutDirection = layoutDirection,
+            transition = transition ?: TransitionImpl.EMPTY,
+            progress = motionProgress.currentProgress
+        )
+        true // Remember is required to return a non-Unit value
+    }
+
+    val measurePolicy = motionLayoutMeasurePolicy(
+        contentTracker = contentTracker,
+        compositionSource = compositionSource,
+        constraintSetStart = start,
+        constraintSetEnd = end,
+        transition = transition ?: TransitionImpl.EMPTY,
+        motionProgress = motionProgress,
+        measurer = measurer,
+        optimizationLevel = optimizationLevel
+    )
+
+    measurer.addLayoutInformationReceiver(informationReceiver)
+
+    val forcedDebug = informationReceiver?.getForcedDrawDebug()
+    val forcedScaleFactor = measurer.forcedScaleFactor
+
+    var doShowBounds = showBounds
+    var doShowPaths = showPaths
+    var doShowKeyPositions = showKeyPositions
+
+    if (forcedDebug != null) {
+        doShowBounds = forcedDebug === MotionLayoutDebugFlags.SHOW_ALL
+        doShowPaths = doShowBounds
+        doShowKeyPositions = doShowBounds
+    }
+
+    @Suppress("DEPRECATION")
+    MultiMeasureLayout(
+        modifier = modifier
+            .motionDebug(
+                measurer = measurer,
+                scaleFactor = forcedScaleFactor,
+                showBounds = doShowBounds,
+                showPaths = doShowPaths,
+                showKeyPositions = doShowKeyPositions
+            )
+            .motionPointerInput(
+                key = transition ?: TransitionImpl.EMPTY,
+                motionProgress = motionProgress,
+                measurer = measurer
+            )
+            .semantics { designInfoProvider = measurer },
+        measurePolicy = measurePolicy,
+        content = {
+            // Perform a reassignment to the State tracker, this will force readers to recompose at
+            // the same pass as the content. The only expected reader is our MeasurePolicy.
+            contentTracker.value = Unit
+
+            if (compositionSource.value == CompositionSource.Unknown) {
+                // Set the content as the original composition source if the MotionLayout was not
+                // recomposed by the caller or by itself
+                compositionSource.value = CompositionSource.Content
+            }
+            scope.content()
+        }
+    )
+}
+
 @LayoutScopeMarker
 @ExperimentalMotionApi
 class MotionLayoutScope @Suppress("ShowingMemberInHiddenClass")
-    @PublishedApi internal constructor(
+@PublishedApi internal constructor(
     private val measurer: MotionMeasurer,
     private val motionProgress: MotionProgress
 ) {
 
     @ExperimentalMotionApi
+    inner class CustomProperties internal constructor(private val id: String) {
+        /**
+         * Return the current [Color] value of the custom property [name], of the [id] layout.
+         *
+         * Returns [Color.Unspecified] if the property does not exist.
+         */
+        fun color(name: String): Color {
+            return measurer.getCustomColor(id, name, motionProgress.currentProgress)
+        }
+
+        /**
+         * Return the current [Color] value of the custom property [name], of the [id] layout.
+         *
+         * Returns [Color.Unspecified] if the property does not exist.
+         */
+        fun float(name: String): Float {
+            return measurer.getCustomFloat(id, name, motionProgress.currentProgress)
+        }
+
+        /**
+         * Return the current [Int] value of the custom property [name], of the [id] layout.
+         *
+         * Returns `0` if the property does not exist.
+         */
+        fun int(name: String): Int {
+            return measurer.getCustomFloat(id, name, motionProgress.currentProgress).toInt()
+        }
+
+        /**
+         * Return the current [Dp] value of the custom property [name], of the [id] layout.
+         *
+         * Returns [Dp.Unspecified] if the property does not exist.
+         */
+        fun distance(name: String): Dp {
+            return measurer.getCustomFloat(id, name, motionProgress.currentProgress).dp
+        }
+
+        /**
+         * Return the current [TextUnit] value of the custom property [name], of the [id] layout.
+         *
+         * Returns [TextUnit.Unspecified] if the property does not exist.
+         */
+        fun fontSize(name: String): TextUnit {
+            return measurer.getCustomFloat(id, name, motionProgress.currentProgress).sp
+        }
+    }
+
+    @ExperimentalMotionApi // TODO: Remove for 1.2.0-alphaXX with all dependent functions
     inner class MotionProperties internal constructor(
         id: String,
         tag: String?
@@ -517,6 +612,10 @@
         }
     }
 
+    @Deprecated(
+        "Unnecessary composable, name is also inconsistent for custom properties",
+        ReplaceWith("customProperties(id)")
+    )
     @Composable
     fun motionProperties(id: String): State<MotionProperties> =
     // TODO: There's no point on returning a [State] object, and probably no point on this being
@@ -525,88 +624,149 @@
             mutableStateOf(MotionProperties(id, null))
         }
 
+    @Deprecated("Deprecated for naming consistency", ReplaceWith("customProperties(id)"))
     fun motionProperties(id: String, tag: String): MotionProperties {
         return MotionProperties(id, tag)
     }
 
+    @Deprecated("Deprecated for naming consistency", ReplaceWith("customColor(id, name)"))
     fun motionColor(id: String, name: String): Color {
         return measurer.getCustomColor(id, name, motionProgress.currentProgress)
     }
 
+    @Deprecated("Deprecated for naming consistency", ReplaceWith("customFloat(id, name)"))
     fun motionFloat(id: String, name: String): Float {
         return measurer.getCustomFloat(id, name, motionProgress.currentProgress)
     }
 
+    @Deprecated("Deprecated for naming consistency", ReplaceWith("customInt(id, name)"))
     fun motionInt(id: String, name: String): Int {
         return measurer.getCustomFloat(id, name, motionProgress.currentProgress).toInt()
     }
 
+    @Deprecated("Deprecated for naming consistency", ReplaceWith("customDistance(id, name)"))
     fun motionDistance(id: String, name: String): Dp {
         return measurer.getCustomFloat(id, name, motionProgress.currentProgress).dp
     }
 
+    @Deprecated("Deprecated for naming consistency", ReplaceWith("customFontSize(id, name)"))
     fun motionFontSize(id: String, name: String): TextUnit {
         return measurer.getCustomFloat(id, name, motionProgress.currentProgress).sp
     }
+
+    /**
+     * Returns a [CustomProperties] instance to access the values of custom properties defined for
+     * [id] in different return types: Color, Float, Int, Dp, TextUnit.
+     *
+     * &nbsp;
+     *
+     * Note that there are no type guarantees when setting or getting custom properties, so be
+     * mindful of the value type used for it in the MotionScene.
+     */
+    fun customProperties(id: String): CustomProperties = CustomProperties(id)
+
+    /**
+     * Return the current [Color] value of the custom property [name], of the [id] layout.
+     *
+     * Returns [Color.Unspecified] if the property does not exist.
+     *
+     * &nbsp;
+     *
+     * This is a short version of: `customProperties(id).color(name)`.
+     */
+    fun customColor(id: String, name: String): Color {
+        return measurer.getCustomColor(id, name, motionProgress.currentProgress)
+    }
+
+    /**
+     * Return the current [Color] value of the custom property [name], of the [id] layout.
+     *
+     * Returns [Color.Unspecified] if the property does not exist.
+     *
+     * &nbsp;
+     *
+     * This is a short version of: `customProperties(id).float(name)`.
+     */
+    fun customFloat(id: String, name: String): Float {
+        return measurer.getCustomFloat(id, name, motionProgress.currentProgress)
+    }
+
+    /**
+     * Return the current [Int] value of the custom property [name], of the [id] layout.
+     *
+     * Returns `0` if the property does not exist.
+     *
+     * &nbsp;
+     *
+     * This is a short version of: `customProperties(id).int(name)`.
+     */
+    fun customInt(id: String, name: String): Int {
+        return measurer.getCustomFloat(id, name, motionProgress.currentProgress).toInt()
+    }
+
+    /**
+     * Return the current [Dp] value of the custom property [name], of the [id] layout.
+     *
+     * Returns [Dp.Unspecified] if the property does not exist.
+     *
+     * &nbsp;
+     *
+     * This is a short version of: `customProperties(id).distance(name)`.
+     */
+    fun customDistance(id: String, name: String): Dp {
+        return measurer.getCustomFloat(id, name, motionProgress.currentProgress).dp
+    }
+
+    /**
+     * Return the current [TextUnit] value of the custom property [name], of the [id] layout.
+     *
+     * Returns [TextUnit.Unspecified] if the property does not exist.
+     *
+     * &nbsp;
+     *
+     * This is a short version of: `customProperties(id).fontSize(name)`.
+     */
+    fun customFontSize(id: String, name: String): TextUnit {
+        return measurer.getCustomFloat(id, name, motionProgress.currentProgress).sp
+    }
 }
 
-enum class MotionLayoutDebugFlags {
-    NONE,
-    SHOW_ALL,
-    UNKNOWN
-}
-
-@Composable
 @PublishedApi
 @ExperimentalMotionApi
-internal fun rememberMotionLayoutMeasurePolicy(
-    optimizationLevel: Int,
-    debug: EnumSet<MotionLayoutDebugFlags>,
+internal fun motionLayoutMeasurePolicy(
+    contentTracker: State<Unit>,
+    compositionSource: Ref<CompositionSource>,
     constraintSetStart: ConstraintSet,
     constraintSetEnd: ConstraintSet,
-    @SuppressWarnings("HiddenTypeParameter") transition: TransitionImpl?,
+    @SuppressWarnings("HiddenTypeParameter") transition: TransitionImpl,
     motionProgress: MotionProgress,
-    motionLayoutFlags: Set<MotionLayoutFlag> = setOf<MotionLayoutFlag>(),
-    measurer: MotionMeasurer
-): MeasurePolicy {
-    val density = LocalDensity.current
-    val layoutDirection = LocalLayoutDirection.current
-    return remember(
-        optimizationLevel,
-        motionLayoutFlags,
-        debug,
-        constraintSetStart,
-        constraintSetEnd,
-        transition
-    ) {
-        measurer.initWith(
+    measurer: MotionMeasurer,
+    optimizationLevel: Int,
+): MeasurePolicy =
+    MeasurePolicy { measurables, constraints ->
+        // Do a state read, to guarantee that we control measure when the content recomposes without
+        // notifying our Composable caller
+        contentTracker.value
+
+        val layoutSize = measurer.performInterpolationMeasure(
+            constraints,
+            this.layoutDirection,
             constraintSetStart,
             constraintSetEnd,
-            density,
-            layoutDirection,
             transition,
-            motionProgress.currentProgress
+            measurables,
+            optimizationLevel,
+            motionProgress.currentProgress,
+            compositionSource.value ?: CompositionSource.Unknown
         )
-        MeasurePolicy { measurables, constraints ->
-            val layoutSize = measurer.performInterpolationMeasure(
-                constraints,
-                layoutDirection,
-                constraintSetStart,
-                constraintSetEnd,
-                transition,
-                measurables,
-                optimizationLevel,
-                motionProgress.currentProgress,
-                motionLayoutFlags
-            )
-            layout(layoutSize.width, layoutSize.height) {
-                with(measurer) {
-                    performLayout(measurables)
-                }
+        compositionSource.value = CompositionSource.Unknown // Reset after measuring
+
+        layout(layoutSize.width, layoutSize.height) {
+            with(measurer) {
+                performLayout(measurables)
             }
         }
     }
-}
 
 /**
  * Updates [motionProgress] from changes in [LayoutInformationReceiver.getForcedProgress].
@@ -644,10 +804,9 @@
  * @param progress User progress, if changed, updates the underlying [MotionProgress]
  * @return A [MotionProgress] instance that may change from internal or external calls
  */
-@Suppress("NOTHING_TO_INLINE")
 @PublishedApi
 @Composable
-internal inline fun createAndUpdateMotionProgress(progress: Float): MotionProgress {
+internal fun createAndUpdateMotionProgress(progress: Float): MotionProgress {
     val motionProgress = remember {
         MotionProgress.fromMutableState(mutableStateOf(progress))
     }
@@ -658,4 +817,52 @@
         motionProgress.updateProgress(progress)
     }
     return motionProgress
+}
+
+@PublishedApi
+@ExperimentalMotionApi
+internal fun Modifier.motionDebug(
+    measurer: MotionMeasurer,
+    scaleFactor: Float,
+    showBounds: Boolean,
+    showPaths: Boolean,
+    showKeyPositions: Boolean
+): Modifier {
+    var debugModifier: Modifier = this
+    if (!scaleFactor.isNaN()) {
+        debugModifier = debugModifier.scale(scaleFactor)
+    }
+    if (showBounds || showKeyPositions || showPaths) {
+        debugModifier = debugModifier.drawBehind {
+            with(measurer) {
+                drawDebug(
+                    drawBounds = showBounds,
+                    drawPaths = showPaths,
+                    drawKeyPositions = showKeyPositions
+                )
+            }
+        }
+    }
+    return debugModifier
+}
+
+/**
+ * Indicates where the composition was initiated.
+ *
+ * The source will help us identify possible pathways for optimization.
+ *
+ * E.g.: If the content was not recomposed, we can assume that previous measurements are still valid,
+ * so there's no need to recalculate the entire interpolation, only the current frame.
+ */
+@PublishedApi
+internal enum class CompositionSource {
+    // TODO: Add an explicit option for Composition initiated internally
+
+    Unknown,
+
+    /**
+     * Content recomposed, need to remeasure everything: **start**, **end** and **interpolated**
+     * states.
+     */
+    Content
 }
\ No newline at end of file
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionMeasurer.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionMeasurer.kt
index 3078295..e768017 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionMeasurer.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionMeasurer.kt
@@ -33,7 +33,6 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.util.fastAny
 import androidx.compose.ui.util.fastForEach
 import androidx.constraintlayout.core.motion.Motion
 import androidx.constraintlayout.core.state.Dimension
@@ -59,6 +58,7 @@
     ) {
         state.reset()
         constraintSet.applyTo(state, measurables)
+        buildMapping(state, measurables)
         state.apply(root)
         root.children.fastForEach { it.isAnimated = true }
         applyRootSize(constraints)
@@ -83,19 +83,13 @@
         layoutDirection: LayoutDirection,
         constraintSetStart: ConstraintSet,
         constraintSetEnd: ConstraintSet,
-        @SuppressWarnings("HiddenTypeParameter") transition: TransitionImpl?,
+        @SuppressWarnings("HiddenTypeParameter") transition: TransitionImpl,
         measurables: List<Measurable>,
         optimizationLevel: Int,
         progress: Float,
-        motionLayoutFlags: Set<MotionLayoutFlag> = setOf<MotionLayoutFlag>()
+        compositionSource: CompositionSource
     ): IntSize {
-        var needsRemeasure = false
-        var flag = motionLayoutFlags.firstOrNull()
-        if (flag == MotionLayoutFlag.Default || flag == null) {
-            needsRemeasure = needsRemeasure(constraints)
-        } else if (flag == MotionLayoutFlag.FullMeasure) {
-            needsRemeasure = true
-        }
+        val needsRemeasure = needsRemeasure(constraints, compositionSource)
 
         if (lastProgressInInterpolation != progress ||
             (layoutInformationReceiver?.getForcedWidth() != Int.MIN_VALUE &&
@@ -125,7 +119,7 @@
      * MotionLayout size might change from its parent Layout, and in some cases the children size
      * might change (eg: A Text layout has a longer string appended).
      */
-    private fun needsRemeasure(constraints: Constraints): Boolean {
+    private fun needsRemeasure(constraints: Constraints, source: CompositionSource): Boolean {
         if (this.transition.isEmpty || frameCache.isEmpty()) {
             // Nothing measured (by MotionMeasurer)
             return true
@@ -138,18 +132,8 @@
             return true
         }
 
-        return root.children.fastAny { child ->
-            // Check if measurables have changed their size
-            val measurable = (child.companionWidget as? Measurable) ?: return@fastAny false
-            val interpolatedFrame = this.transition.getInterpolated(child) ?: return@fastAny false
-            val placeable = placeables[measurable] ?: return@fastAny false
-            val currentWidth = placeable.width
-            val currentHeight = placeable.height
-
-            // Need to recalculate interpolation if the size of any element changed
-            return@fastAny currentWidth != interpolatedFrame.width() ||
-                currentHeight != interpolatedFrame.height()
-        }
+        // Content recomposed
+        return source == CompositionSource.Content
     }
 
     /**
@@ -174,7 +158,6 @@
         if (remeasure) {
             this.transition.clear()
             resetMeasureState()
-            state.reset()
             // Define the size of the ConstraintLayout.
             state.width(
                 if (constraints.hasFixedWidth) {
@@ -203,28 +186,24 @@
             )
             this.transition.updateFrom(root, Transition.END)
             transition?.applyKeyFramesTo(this.transition)
+        } else {
+            // Have to remap even if there's no reason to remeasure
+            buildMapping(state, measurables)
         }
-
         this.transition.interpolate(root.width, root.height, progress)
         root.width = this.transition.interpolatedWidth
         root.height = this.transition.interpolatedHeight
+        // Update measurables to interpolated dimensions
         root.children.fastForEach { child ->
             // Update measurables to the interpolated dimensions
             val measurable = (child.companionWidget as? Measurable) ?: return@fastForEach
             val interpolatedFrame = this.transition.getInterpolated(child) ?: return@fastForEach
-            val placeable = placeables[measurable]
-            val currentWidth = placeable?.width
-            val currentHeight = placeable?.height
-            if (placeable == null ||
-                currentWidth != interpolatedFrame.width() ||
-                currentHeight != interpolatedFrame.height()
-            ) {
-                measurable.measure(
-                    Constraints.fixed(interpolatedFrame.width(), interpolatedFrame.height())
-                ).also { newPlaceable ->
-                    placeables[measurable] = newPlaceable
-                }
-            }
+            placeables[measurable] = measurable.measure(
+                Constraints.fixed(
+                    interpolatedFrame.width(),
+                    interpolatedFrame.height()
+                )
+            )
             frameCache[measurable] = interpolatedFrame
         }
 
@@ -310,34 +289,61 @@
         layoutInformationReceiver?.setLayoutInformation(json.toString())
     }
 
-    fun DrawScope.drawDebug() {
-        var index = 0
+    /**
+     * Draws debug information related to the current Transition.
+     *
+     * Typically, this means drawing the bounds of each widget at the start/end positions, the path
+     * they take and indicators for KeyPositions.
+     */
+    fun DrawScope.drawDebug(
+        drawBounds: Boolean = true,
+        drawPaths: Boolean = true,
+        drawKeyPositions: Boolean = true,
+    ) {
         val pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f)
+
         for (child in root.children) {
             val startFrame = transition.getStart(child)
             val endFrame = transition.getEnd(child)
-            translate(2f, 2f) {
-                drawFrameDebug(
-                    size.width,
-                    size.height,
-                    startFrame,
-                    endFrame,
-                    pathEffect,
-                    Color.White
-                )
+            if (drawBounds) {
+                // Draw widget bounds at the start and end
+                drawFrame(frame = startFrame, pathEffect = pathEffect, color = Color.Blue)
+                drawFrame(frame = endFrame, pathEffect = pathEffect, color = Color.Blue)
+                translate(2f, 2f) {
+                    // Do an additional offset draw in case the bounds are not visible/obstructed
+                    drawFrame(frame = startFrame, pathEffect = pathEffect, color = Color.White)
+                    drawFrame(frame = endFrame, pathEffect = pathEffect, color = Color.White)
+                }
             }
-            drawFrameDebug(
-                size.width,
-                size.height,
-                startFrame,
-                endFrame,
-                pathEffect,
-                Color.Blue
+            drawPaths(
+                parentWidth = size.width,
+                parentHeight = size.height,
+                startFrame = startFrame,
+                drawPath = drawPaths,
+                drawKeyPositions = drawKeyPositions
             )
-            index++
         }
     }
 
+    private fun DrawScope.drawPaths(
+        parentWidth: Float,
+        parentHeight: Float,
+        startFrame: WidgetFrame,
+        drawPath: Boolean,
+        drawKeyPositions: Boolean
+    ) {
+        val debugRender = MotionRenderDebug(23f)
+        debugRender.basicDraw(
+            drawContext.canvas.nativeCanvas,
+            transition.getMotion(startFrame.widget.stringId),
+            1000,
+            parentWidth.toInt(),
+            parentHeight.toInt(),
+            drawPath,
+            drawKeyPositions
+        )
+    }
+
     private fun DrawScope.drawFrameDebug(
         parentWidth: Float,
         parentHeight: Float,
@@ -475,17 +481,20 @@
      * ConstraintWidget corresponding to [id], the value is calculated at the given [progress] value
      * on the current Transition.
      *
-     * Returns [Color.Black] if the custom property doesn't exist.
+     * Returns [Color.Unspecified] if the custom property doesn't exist.
      */
     fun getCustomColor(id: String, name: String, progress: Float): Color {
         if (!transition.contains(id)) {
-            return Color.Black
+            return Color.Unspecified
         }
         transition.interpolate(root.width, root.height, progress)
 
         val interpolatedFrame = transition.getInterpolated(id)
-        val color = interpolatedFrame.getCustomColor(name)
-        return Color(color)
+
+        if (!interpolatedFrame.containsCustom(name)) {
+            return Color.Unspecified
+        }
+        return Color(interpolatedFrame.getCustomColor(name))
     }
 
     /**
@@ -493,13 +502,14 @@
      * ConstraintWidget corresponding to [id], the value is calculated at the given [progress] value
      * on the current Transition.
      *
-     * Returns `0f` if the custom property doesn't exist.
+     * Returns [Float.NaN] if the custom property doesn't exist.
      */
     fun getCustomFloat(id: String, name: String, progress: Float): Float {
         if (!transition.contains(id)) {
-            return 0f
+            return Float.NaN
         }
         transition.interpolate(root.width, root.height, progress)
+
         val interpolatedFrame = transition.getInterpolated(id)
         return interpolatedFrame.getCustomFloat(name)
     }
@@ -513,28 +523,24 @@
     fun initWith(
         start: ConstraintSet,
         end: ConstraintSet,
-        density: Density,
         layoutDirection: LayoutDirection,
-        @SuppressWarnings("HiddenTypeParameter") transition: TransitionImpl?,
+        @SuppressWarnings("HiddenTypeParameter") transition: TransitionImpl,
         progress: Float
     ) {
         clearConstraintSets()
 
-        // FIXME: tempState is a hack to populate initial custom properties with DSL
-        val tempState = State(density).apply {
-            this.isLtr = layoutDirection == LayoutDirection.Ltr
-        }
-        start.applyTo(tempState, emptyList())
+        state.isLtr = layoutDirection == LayoutDirection.Ltr
+        start.applyTo(state, emptyList())
         start.applyTo(this.transition, Transition.START)
-        tempState.apply(root)
+        state.apply(root)
         this.transition.updateFrom(root, Transition.START)
 
-        start.applyTo(tempState, emptyList())
+        start.applyTo(state, emptyList())
         end.applyTo(this.transition, Transition.END)
-        tempState.apply(root)
+        state.apply(root)
         this.transition.updateFrom(root, Transition.END)
 
         this.transition.interpolate(0, 0, progress)
-        transition?.applyAllTo(this.transition)
+        transition.applyAllTo(this.transition)
     }
 }
\ No newline at end of file
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionRenderDebug.java b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionRenderDebug.java
index 4302c7c..e259358 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionRenderDebug.java
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionRenderDebug.java
@@ -21,6 +21,7 @@
 import android.graphics.Path;
 import android.graphics.Rect;
 
+import androidx.annotation.NonNull;
 import androidx.constraintlayout.core.motion.Motion;
 import androidx.constraintlayout.core.motion.MotionPaths;
 
@@ -170,6 +171,63 @@
         drawTicks(canvas, mode, keyFrames, motionController, layoutWidth, layoutHeight);
     }
 
+
+    /**
+     * Draws the paths of the given {@link Motion motionController}, forcing the drawing mode
+     * {@link Motion#DRAW_PATH_BASIC}.
+     *
+     * @param canvas Canvas instance used to draw on
+     * @param motionController Controller containing path information
+     * @param duration Defined in milliseconds, sets the amount of ticks used to draw the path
+     *                 based on {@link #DEBUG_PATH_TICKS_PER_MS}
+     * @param layoutWidth Width of the containing MotionLayout
+     * @param layoutHeight Height of the containing MotionLayout
+     * @param drawPath Whether to draw the path, paths are drawn using dashed lines
+     * @param drawTicks Whether to draw diamond shaped ticks that indicate KeyPositions along a path
+     */
+    void basicDraw(@NonNull Canvas canvas,
+            @NonNull Motion motionController,
+            int duration,
+            int layoutWidth,
+            int layoutHeight,
+            boolean drawPath,
+            boolean drawTicks) {
+        int mode = Motion.DRAW_PATH_BASIC;
+        mKeyFrameCount = motionController.buildKeyFrames(mKeyFramePoints, mPathMode, null);
+
+        int frames = duration / DEBUG_PATH_TICKS_PER_MS;
+        if (mPoints == null || mPoints.length != frames * 2) {
+            mPoints = new float[frames * 2];
+            mPath = new Path();
+        }
+
+        canvas.translate(mShadowTranslate, mShadowTranslate);
+
+        mPaint.setColor(mShadowColor);
+        mFillPaint.setColor(mShadowColor);
+        mPaintKeyframes.setColor(mShadowColor);
+        mPaintGraph.setColor(mShadowColor);
+        motionController.buildPath(mPoints, frames);
+        if (drawPath) {
+            drawBasicPath(canvas);
+        }
+        if (drawTicks) {
+            drawTicks(canvas, mode, mKeyFrameCount, motionController, layoutWidth, layoutHeight);
+        }
+
+        mPaint.setColor(mRedColor);
+        mPaintKeyframes.setColor(mKeyframeColor);
+        mFillPaint.setColor(mKeyframeColor);
+        mPaintGraph.setColor(mGraphColor);
+        canvas.translate(-mShadowTranslate, -mShadowTranslate);
+        if (drawPath) {
+            drawBasicPath(canvas);
+        }
+        if (drawTicks) {
+            drawTicks(canvas, mode, mKeyFrameCount, motionController, layoutWidth, layoutHeight);
+        }
+    }
+
     private void drawBasicPath(Canvas canvas) {
         canvas.drawLines(mPoints, mPaint);
     }
@@ -376,5 +434,4 @@
         mPaint.setColor(0xFFFF0000);
         canvas.drawPath(mPath, mPaint);
     }
-
-}
+}
\ No newline at end of file
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionSceneScope.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionSceneScope.kt
index 35287d9..0b17e86 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionSceneScope.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/MotionSceneScope.kt
@@ -126,11 +126,18 @@
     private var generatedCount = 0
 
     /**
+     * Count of generated ConstraintLayoutReference IDs.
+     */
+    private var generatedIdCount = 0
+
+    /**
      * Returns a new unique name. Should be used when the user does not provide a specific name
      * for their ConstraintSets/Transitions.
      */
     private fun nextName() = UNDEFINED_NAME_PREFIX + generatedCount++
 
+    private fun nextId() = UNDEFINED_NAME_PREFIX + "id${generatedIdCount++}"
+
     internal var constraintSetsByName = HashMap<String, ConstraintSet>()
     internal var transitionsByName = HashMap<String, Transition>()
 
@@ -243,6 +250,75 @@
     fun createRefFor(id: Any): ConstrainedLayoutReference = ConstrainedLayoutReference(id)
 
     /**
+     * Convenient way to create multiple [ConstrainedLayoutReference] with one statement, the [ids]
+     * provided should match Composables within ConstraintLayout using [androidx.compose.ui.Modifier.layoutId].
+     *
+     * Example:
+     * ```
+     * val (box, text, button) = createRefsFor("box", "text", "button")
+     * ```
+     * Note that the number of ids should match the number of variables assigned.
+     *
+     * &nbsp;
+     *
+     * To create a singular [ConstrainedLayoutReference] see [createRefFor].
+     */
+    fun createRefsFor(vararg ids: Any): ConstrainedLayoutReferences =
+        ConstrainedLayoutReferences(arrayOf(*ids))
+
+    inner class ConstrainedLayoutReferences internal constructor(
+        private val ids: Array<Any>
+    ) {
+        operator fun component1(): ConstrainedLayoutReference =
+            ConstrainedLayoutReference(ids.getOrElse(0) { nextId() })
+
+        operator fun component2(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(1) { nextId() })
+
+        operator fun component3(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(2) { nextId() })
+
+        operator fun component4(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(3) { nextId() })
+
+        operator fun component5(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(4) { nextId() })
+
+        operator fun component6(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(5) { nextId() })
+
+        operator fun component7(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(6) { nextId() })
+
+        operator fun component8(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(7) { nextId() })
+
+        operator fun component9(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(8) { nextId() })
+
+        operator fun component10(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(9) { nextId() })
+
+        operator fun component11(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(10) { nextId() })
+
+        operator fun component12(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(11) { nextId() })
+
+        operator fun component13(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(12) { nextId() })
+
+        operator fun component14(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(13) { nextId() })
+
+        operator fun component15(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(14) { nextId() })
+
+        operator fun component16(): ConstrainedLayoutReference =
+            createRefFor(ids.getOrElse(15) { nextId() })
+    }
+
+    /**
      * Declare a custom Float [value] addressed by [name].
      */
     fun ConstrainScope.customFloat(name: String, value: Float) {
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/Transition.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/Transition.kt
index 131c043..2c69672 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/Transition.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/Transition.kt
@@ -58,6 +58,7 @@
  * Used to reduced the exposed API from [Transition].
  */
 @ExperimentalMotionApi
+@PublishedApi
 internal class TransitionImpl(
     private val parsedTransition: CLObject
 ) : Transition {
@@ -108,7 +109,9 @@
         return parsedTransition.hashCode()
     }
 
+    @PublishedApi
     internal companion object {
+        @PublishedApi
         internal val EMPTY = TransitionImpl(CLObject(charArrayOf()))
     }
 }
\ No newline at end of file
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/TransitionScope.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/TransitionScope.kt
index bc1adf0..fdc6605 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/TransitionScope.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/TransitionScope.kt
@@ -401,6 +401,8 @@
         val StartVertical = Arc("startVertical")
         val StartHorizontal = Arc("startHorizontal")
         val Flip = Arc("flip")
+        val Below = Arc("below")
+        val Above = Arc("above")
     }
 }
 
@@ -510,4 +512,4 @@
         val Path: RelativePosition = RelativePosition("pathRelative")
         val Parent: RelativePosition = RelativePosition("parentRelative")
     }
-}
\ No newline at end of file
+}
diff --git a/constraintlayout/constraintlayout-core/api/current.txt b/constraintlayout/constraintlayout-core/api/current.txt
index d7b2374..a73dc5b 100644
--- a/constraintlayout/constraintlayout-core/api/current.txt
+++ b/constraintlayout/constraintlayout-core/api/current.txt
@@ -1250,6 +1250,8 @@
     method public void getSlope(double, double[]!);
     method public double getSlope(double, int);
     method public double[]! getTimePoints();
+    field public static final int ARC_ABOVE = 5; // 0x5
+    field public static final int ARC_BELOW = 4; // 0x4
     field public static final int ARC_START_FLIP = 3; // 0x3
     field public static final int ARC_START_HORIZONTAL = 2; // 0x2
     field public static final int ARC_START_LINEAR = 0; // 0x0
@@ -2299,6 +2301,7 @@
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BASELINE_TO_BASELINE;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BASELINE_TO_BOTTOM;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BASELINE_TO_TOP;
+    enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BOTTOM_TO_BASELINE;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BOTTOM_TO_BOTTOM;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BOTTOM_TO_TOP;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint CENTER_HORIZONTALLY;
@@ -2312,6 +2315,7 @@
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint RIGHT_TO_RIGHT;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint START_TO_END;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint START_TO_START;
+    enum_constant public static final androidx.constraintlayout.core.state.State.Constraint TOP_TO_BASELINE;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint TOP_TO_BOTTOM;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint TOP_TO_TOP;
   }
@@ -2427,6 +2431,7 @@
     method public void addCustomFloat(String!, float);
     method public float centerX();
     method public float centerY();
+    method public boolean containsCustom(String);
     method public androidx.constraintlayout.core.motion.CustomVariable! getCustomAttribute(String!);
     method public java.util.Set<java.lang.String!>! getCustomAttributeNames();
     method public int getCustomColor(String!);
diff --git a/constraintlayout/constraintlayout-core/api/public_plus_experimental_current.txt b/constraintlayout/constraintlayout-core/api/public_plus_experimental_current.txt
index d7b2374..a73dc5b 100644
--- a/constraintlayout/constraintlayout-core/api/public_plus_experimental_current.txt
+++ b/constraintlayout/constraintlayout-core/api/public_plus_experimental_current.txt
@@ -1250,6 +1250,8 @@
     method public void getSlope(double, double[]!);
     method public double getSlope(double, int);
     method public double[]! getTimePoints();
+    field public static final int ARC_ABOVE = 5; // 0x5
+    field public static final int ARC_BELOW = 4; // 0x4
     field public static final int ARC_START_FLIP = 3; // 0x3
     field public static final int ARC_START_HORIZONTAL = 2; // 0x2
     field public static final int ARC_START_LINEAR = 0; // 0x0
@@ -2299,6 +2301,7 @@
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BASELINE_TO_BASELINE;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BASELINE_TO_BOTTOM;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BASELINE_TO_TOP;
+    enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BOTTOM_TO_BASELINE;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BOTTOM_TO_BOTTOM;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BOTTOM_TO_TOP;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint CENTER_HORIZONTALLY;
@@ -2312,6 +2315,7 @@
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint RIGHT_TO_RIGHT;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint START_TO_END;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint START_TO_START;
+    enum_constant public static final androidx.constraintlayout.core.state.State.Constraint TOP_TO_BASELINE;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint TOP_TO_BOTTOM;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint TOP_TO_TOP;
   }
@@ -2427,6 +2431,7 @@
     method public void addCustomFloat(String!, float);
     method public float centerX();
     method public float centerY();
+    method public boolean containsCustom(String);
     method public androidx.constraintlayout.core.motion.CustomVariable! getCustomAttribute(String!);
     method public java.util.Set<java.lang.String!>! getCustomAttributeNames();
     method public int getCustomColor(String!);
diff --git a/constraintlayout/constraintlayout-core/api/restricted_current.txt b/constraintlayout/constraintlayout-core/api/restricted_current.txt
index 0bdeacc..8b557da 100644
--- a/constraintlayout/constraintlayout-core/api/restricted_current.txt
+++ b/constraintlayout/constraintlayout-core/api/restricted_current.txt
@@ -1250,6 +1250,8 @@
     method public void getSlope(double, double[]!);
     method public double getSlope(double, int);
     method public double[]! getTimePoints();
+    field public static final int ARC_ABOVE = 5; // 0x5
+    field public static final int ARC_BELOW = 4; // 0x4
     field public static final int ARC_START_FLIP = 3; // 0x3
     field public static final int ARC_START_HORIZONTAL = 2; // 0x2
     field public static final int ARC_START_LINEAR = 0; // 0x0
@@ -2300,6 +2302,7 @@
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BASELINE_TO_BASELINE;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BASELINE_TO_BOTTOM;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BASELINE_TO_TOP;
+    enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BOTTOM_TO_BASELINE;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BOTTOM_TO_BOTTOM;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint BOTTOM_TO_TOP;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint CENTER_HORIZONTALLY;
@@ -2313,6 +2316,7 @@
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint RIGHT_TO_RIGHT;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint START_TO_END;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint START_TO_START;
+    enum_constant public static final androidx.constraintlayout.core.state.State.Constraint TOP_TO_BASELINE;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint TOP_TO_BOTTOM;
     enum_constant public static final androidx.constraintlayout.core.state.State.Constraint TOP_TO_TOP;
   }
@@ -2429,6 +2433,7 @@
     method public void addCustomFloat(String!, float);
     method public float centerX();
     method public float centerY();
+    method public boolean containsCustom(String);
     method public androidx.constraintlayout.core.motion.CustomVariable! getCustomAttribute(String!);
     method public java.util.Set<java.lang.String!>! getCustomAttributeNames();
     method public int getCustomColor(String!);
diff --git a/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/motion/MotionPaths.java b/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/motion/MotionPaths.java
index a802192..1b7ad89 100644
--- a/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/motion/MotionPaths.java
+++ b/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/motion/MotionPaths.java
@@ -28,8 +28,6 @@
 /**
  * This is used to capture and play back path of the layout.
  * It is used to set the bounds of the view (view.layout(l, t, r, b))
- *
- *
  */
 public class MotionPaths implements Comparable<MotionPaths> {
     public static final String TAG = "MotionPaths";
@@ -68,7 +66,6 @@
     int mAnimateCircleAngleTo; // since angles loop there are 4 ways we can pic direction
 
     public MotionPaths() {
-
     }
 
     /**
diff --git a/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/motion/utils/ArcCurveFit.java b/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/motion/utils/ArcCurveFit.java
index bf1a4c0..7df5ef9 100644
--- a/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/motion/utils/ArcCurveFit.java
+++ b/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/motion/utils/ArcCurveFit.java
@@ -27,11 +27,17 @@
     public static final int ARC_START_VERTICAL = 1;
     public static final int ARC_START_HORIZONTAL = 2;
     public static final int ARC_START_FLIP = 3;
+    public static final int ARC_BELOW = 4;
+    public static final int ARC_ABOVE = 5;
+
     public static final int ARC_START_LINEAR = 0;
 
     private static final int START_VERTICAL = 1;
     private static final int START_HORIZONTAL = 2;
     private static final int START_LINEAR = 3;
+    private static final int DOWN_ARC = 4;
+    private static final int UP_ARC = 5;
+
     private final double[] mTime;
     Arc[] mArcs;
     private boolean mExtrapolate = true;
@@ -275,6 +281,13 @@
                     break;
                 case ARC_START_LINEAR:
                     mode = START_LINEAR;
+                    break;
+                case ARC_ABOVE:
+                    mode = UP_ARC;
+                    break;
+                case ARC_BELOW:
+                    mode = DOWN_ARC;
+                    break;
             }
             mArcs[i] =
                     new Arc(mode, time[i], time[i + 1], y[i][0], y[i][1], y[i + 1][0], y[i + 1][1]);
@@ -302,15 +315,29 @@
         private static final double EPSILON = 0.001;
 
         Arc(int mode, double t1, double t2, double x1, double y1, double x2, double y2) {
-            mVertical = mode == START_VERTICAL;
+            double dx = x2 - x1;
+            double dy = y2 - y1;
+            switch (mode) {
+                case START_VERTICAL:
+                    mVertical = true;
+                    break;
+                case UP_ARC:
+                    mVertical = dy < 0;
+                    break;
+                case DOWN_ARC:
+                    mVertical = dy > 0;
+                    break;
+                default:
+                    mVertical = false;
+            }
+
             mTime1 = t1;
             mTime2 = t2;
             mOneOverDeltaTime = 1 / (mTime2 - mTime1);
             if (START_LINEAR == mode) {
                 mLinear = true;
             }
-            double dx = x2 - x1;
-            double dy = y2 - y1;
+
             if (mLinear || Math.abs(dx) < EPSILON || Math.abs(dy) < EPSILON) {
                 mLinear = true;
                 mX1 = x1;
diff --git a/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/ConstraintReference.java b/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/ConstraintReference.java
index 4a5a852..53e09a9 100644
--- a/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/ConstraintReference.java
+++ b/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/ConstraintReference.java
@@ -20,6 +20,7 @@
 import static androidx.constraintlayout.core.widgets.ConstraintWidget.UNKNOWN;
 import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL;
 
+import androidx.annotation.Nullable;
 import androidx.constraintlayout.core.motion.utils.TypedBundle;
 import androidx.constraintlayout.core.motion.utils.TypedValues;
 import androidx.constraintlayout.core.state.helpers.Facade;
@@ -116,8 +117,10 @@
     protected Object mEndToEnd = null;
     protected Object mTopToTop = null;
     protected Object mTopToBottom = null;
+    @Nullable Object mTopToBaseline = null;
     protected Object mBottomToTop = null;
     protected Object mBottomToBottom = null;
+    @Nullable Object mBottomToBaseline = null;
     Object mBaselineToBaseline = null;
     Object mBaselineToTop = null;
     Object mBaselineToBottom = null;
@@ -585,6 +588,12 @@
         return this;
     }
 
+    ConstraintReference topToBaseline(Object reference) {
+        mLast = State.Constraint.TOP_TO_BASELINE;
+        mTopToBaseline = reference;
+        return this;
+    }
+
     // @TODO: add description
     public ConstraintReference bottomToTop(Object reference) {
         mLast = State.Constraint.BOTTOM_TO_TOP;
@@ -599,6 +608,12 @@
         return this;
     }
 
+    ConstraintReference bottomToBaseline(Object reference) {
+        mLast = State.Constraint.BOTTOM_TO_BASELINE;
+        mBottomToBaseline = reference;
+        return this;
+    }
+
     // @TODO: add description
     public ConstraintReference baselineToBaseline(Object reference) {
         mLast = State.Constraint.BASELINE_TO_BASELINE;
@@ -715,12 +730,14 @@
                 }
                 break;
                 case TOP_TO_TOP:
-                case TOP_TO_BOTTOM: {
+                case TOP_TO_BOTTOM:
+                case TOP_TO_BASELINE: {
                     mMarginTop = value;
                 }
                 break;
                 case BOTTOM_TO_TOP:
-                case BOTTOM_TO_BOTTOM: {
+                case BOTTOM_TO_BOTTOM:
+                case BOTTOM_TO_BASELINE: {
                     mMarginBottom = value;
                 }
                 break;
@@ -773,12 +790,14 @@
                 }
                 break;
                 case TOP_TO_TOP:
-                case TOP_TO_BOTTOM: {
+                case TOP_TO_BOTTOM:
+                case TOP_TO_BASELINE: {
                     mMarginTopGone = value;
                 }
                 break;
                 case BOTTOM_TO_TOP:
-                case BOTTOM_TO_BOTTOM: {
+                case BOTTOM_TO_BOTTOM:
+                case BOTTOM_TO_BASELINE: {
                     mMarginBottomGone = value;
                 }
                 break;
@@ -835,8 +854,10 @@
             case CENTER_VERTICALLY:
             case TOP_TO_TOP:
             case TOP_TO_BOTTOM:
+            case TOP_TO_BASELINE:
             case BOTTOM_TO_TOP:
-            case BOTTOM_TO_BOTTOM: {
+            case BOTTOM_TO_BOTTOM:
+            case BOTTOM_TO_BASELINE: {
                 mVerticalBias = value;
             }
             break;
@@ -918,17 +939,21 @@
                 }
                 break;
                 case TOP_TO_TOP:
-                case TOP_TO_BOTTOM: {
+                case TOP_TO_BOTTOM:
+                case TOP_TO_BASELINE: {
                     mTopToTop = null;
                     mTopToBottom = null;
+                    mTopToBaseline = null;
                     mMarginTop = 0;
                     mMarginTopGone = 0;
                 }
                 break;
                 case BOTTOM_TO_TOP:
-                case BOTTOM_TO_BOTTOM: {
+                case BOTTOM_TO_BOTTOM:
+                case BOTTOM_TO_BASELINE: {
                     mBottomToTop = null;
                     mBottomToBottom = null;
+                    mBottomToBaseline = null;
                     mMarginBottom = 0;
                     mMarginBottomGone = 0;
                 }
@@ -1021,6 +1046,11 @@
                         ConstraintAnchor.Type.BOTTOM), mMarginTop, mMarginTopGone, false);
             }
             break;
+            case TOP_TO_BASELINE: {
+                widget.immediateConnect(ConstraintAnchor.Type.TOP, target,
+                        ConstraintAnchor.Type.BASELINE, mMarginTop, mMarginTopGone);
+            }
+            break;
             case BOTTOM_TO_TOP: {
                 widget.getAnchor(ConstraintAnchor.Type.BOTTOM).connect(target.getAnchor(
                         ConstraintAnchor.Type.TOP), mMarginBottom, mMarginBottomGone, false);
@@ -1031,6 +1061,11 @@
                         ConstraintAnchor.Type.BOTTOM), mMarginBottom, mMarginBottomGone, false);
             }
             break;
+            case BOTTOM_TO_BASELINE: {
+                widget.immediateConnect(ConstraintAnchor.Type.BOTTOM, target,
+                        ConstraintAnchor.Type.BASELINE, mMarginBottom, mMarginBottomGone);
+            }
+            break;
             case BASELINE_TO_BASELINE: {
                 widget.immediateConnect(ConstraintAnchor.Type.BASELINE, target,
                         ConstraintAnchor.Type.BASELINE, mMarginBaseline, mMarginBaselineGone);
@@ -1069,8 +1104,10 @@
         applyConnection(mConstraintWidget, mEndToEnd, State.Constraint.END_TO_END);
         applyConnection(mConstraintWidget, mTopToTop, State.Constraint.TOP_TO_TOP);
         applyConnection(mConstraintWidget, mTopToBottom, State.Constraint.TOP_TO_BOTTOM);
+        applyConnection(mConstraintWidget, mTopToBaseline, State.Constraint.TOP_TO_BASELINE);
         applyConnection(mConstraintWidget, mBottomToTop, State.Constraint.BOTTOM_TO_TOP);
         applyConnection(mConstraintWidget, mBottomToBottom, State.Constraint.BOTTOM_TO_BOTTOM);
+        applyConnection(mConstraintWidget, mBottomToBaseline, State.Constraint.BOTTOM_TO_BASELINE);
         applyConnection(mConstraintWidget, mBaselineToBaseline,
                 State.Constraint.BASELINE_TO_BASELINE);
         applyConnection(mConstraintWidget, mBaselineToTop, State.Constraint.BASELINE_TO_TOP);
diff --git a/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/ConstraintSetParser.java b/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/ConstraintSetParser.java
index c425c62..2cdabc5 100644
--- a/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/ConstraintSetParser.java
+++ b/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/ConstraintSetParser.java
@@ -1727,7 +1727,8 @@
             switch (constraintName) {
                 case "pathArc":
                     String val = obj.getString(constraintName);
-                    int ord = indexOf(val, "none", "startVertical", "startHorizontal", "flip");
+                    int ord = indexOf(val, "none", "startVertical", "startHorizontal", "flip",
+                            "below", "above");
                     if (ord == -1) {
                         System.err.println(obj.getLine() + " pathArc = '" + val + "'");
                         break;
@@ -1822,6 +1823,11 @@
                             break;
                         case "bottom":
                             reference.topToBottom(targetReference);
+                            break;
+                        case "baseline":
+                            state.baselineNeededFor(targetReference.getKey());
+                            reference.topToBaseline(targetReference);
+                            break;
                     }
                     break;
                 case "bottom":
@@ -1831,6 +1837,10 @@
                             break;
                         case "bottom":
                             reference.bottomToBottom(targetReference);
+                            break;
+                        case "baseline":
+                            state.baselineNeededFor(targetReference.getKey());
+                            reference.bottomToBaseline(targetReference);
                     }
                     break;
                 case "baseline":
@@ -1842,12 +1852,10 @@
                             break;
                         case "top":
                             state.baselineNeededFor(reference.getKey());
-                            state.baselineNeededFor(targetReference.getKey());
                             reference.baselineToTop(targetReference);
                             break;
                         case "bottom":
                             state.baselineNeededFor(reference.getKey());
-                            state.baselineNeededFor(targetReference.getKey());
                             reference.baselineToBottom(targetReference);
                             break;
                     }
diff --git a/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/State.java b/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/State.java
index d083aa7..3ea521d 100644
--- a/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/State.java
+++ b/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/State.java
@@ -67,8 +67,10 @@
         END_TO_END,
         TOP_TO_TOP,
         TOP_TO_BOTTOM,
+        TOP_TO_BASELINE,
         BOTTOM_TO_TOP,
         BOTTOM_TO_BOTTOM,
+        BOTTOM_TO_BASELINE,
         BASELINE_TO_BASELINE,
         BASELINE_TO_TOP,
         BASELINE_TO_BOTTOM,
@@ -186,6 +188,7 @@
     }
 
     public State() {
+        mParent.setKey(PARENT);
         mReferences.put(PARENT, mParent);
     }
 
@@ -238,7 +241,7 @@
      */
     public int convertDimension(Object value) {
         if (value instanceof Float) {
-            return (int) (((Float) value) + 0.5f);
+            return Math.round((Float) value);
         }
         if (value instanceof Integer) {
             return (Integer) value;
diff --git a/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/TransitionParser.java b/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/TransitionParser.java
index cf3eba7..66e351a 100644
--- a/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/TransitionParser.java
+++ b/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/TransitionParser.java
@@ -76,6 +76,12 @@
                     break;
                 case "flip":
                     bundle.add(TypedValues.PositionType.TYPE_PATH_MOTION_ARC, 3);
+                    break;
+                case "below":
+                    bundle.add(TypedValues.PositionType.TYPE_PATH_MOTION_ARC, 4);
+                    break;
+                case "above":
+                    bundle.add(TypedValues.PositionType.TYPE_PATH_MOTION_ARC, 5);
             }
 
         }
@@ -235,7 +241,7 @@
 
             if (pathMotionArc != null) {
                 map(bundle, TypedValues.PositionType.TYPE_PATH_MOTION_ARC, pathMotionArc,
-                        "none", "startVertical", "startHorizontal", "flip");
+                        "none", "startVertical", "startHorizontal", "flip", "below", "above");
             }
 
             for (int j = 0; j < frames.size(); j++) {
diff --git a/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/WidgetFrame.java b/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/WidgetFrame.java
index 5406294..4adf478 100644
--- a/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/WidgetFrame.java
+++ b/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/WidgetFrame.java
@@ -16,6 +16,7 @@
 
 package androidx.constraintlayout.core.state;
 
+import androidx.annotation.NonNull;
 import androidx.constraintlayout.core.motion.CustomAttribute;
 import androidx.constraintlayout.core.motion.CustomVariable;
 import androidx.constraintlayout.core.motion.utils.TypedBundle;
@@ -320,6 +321,13 @@
         return this;
     }
 
+    /**
+     * Return whether this WidgetFrame contains a custom property of the given name.
+     */
+    public boolean containsCustom(@NonNull String name) {
+        return mCustom.containsKey(name);
+    }
+
     // @TODO: add description
     public void addCustomColor(String name, int color) {
         setCustomAttribute(name, TypedValues.Custom.TYPE_COLOR, color);
diff --git a/constraintlayout/constraintlayout-core/src/test/java/androidx/constraintlayout/core/motion/MotionArcCurveTest.java b/constraintlayout/constraintlayout-core/src/test/java/androidx/constraintlayout/core/motion/MotionArcCurveTest.java
index e45efda..d5b2812 100644
--- a/constraintlayout/constraintlayout-core/src/test/java/androidx/constraintlayout/core/motion/MotionArcCurveTest.java
+++ b/constraintlayout/constraintlayout-core/src/test/java/androidx/constraintlayout/core/motion/MotionArcCurveTest.java
@@ -22,6 +22,8 @@
 
 import org.junit.Test;
 
+import java.util.Arrays;
+
 public class MotionArcCurveTest {
     @Test
     public void arcTest1() {
@@ -53,4 +55,142 @@
         assertEquals(1 - Math.sqrt(0.5), x, 0.001);
         assertEquals(Math.sqrt(0.5), y, 0.001);
     }
+
+    @Test
+    public void arcTest2() {
+        double[][] points = {
+                {0, 0}, {1, 1}, {2, 0}
+        };
+        double[] time = {
+                0, 5, 10
+        };
+        int[] mode = {
+                ArcCurveFit.ARC_BELOW,
+                ArcCurveFit.ARC_BELOW,
+
+        };
+        CurveFit spline = CurveFit.getArc(mode, time, points);
+        System.out.println("");
+        for (int i = 0; i < time.length; i++) {
+            assertEquals(points[i][0], spline.getPos(time[i], 0), 0.001);
+            assertEquals(points[i][1], spline.getPos(time[i], 1), 0.001);
+        }
+        assertEquals(0, spline.getSlope(time[0] + 0.01, 0), 0.001);
+        assertEquals(0, spline.getSlope(time[1] - 0.01, 1), 0.001);
+        assertEquals(0, spline.getSlope(time[1] + 0.01, 1), 0.001);
+        double dx = spline.getSlope((time[0] + time[1]) / 2, 0);
+        double dy = spline.getSlope((time[0] + time[1]) / 2, 1);
+        assertEquals(1, dx / dy, 0.001);
+        double x = spline.getPos((time[0] + time[1]) / 2, 0);
+        double y = spline.getPos((time[0] + time[1]) / 2, 1);
+        assertEquals(1 - Math.sqrt(0.5), x, 0.001);
+        assertEquals(Math.sqrt(0.5), y, 0.001);
+    }
+
+    @Test
+    public void arcTest3() {
+        double[][] points = {
+                {0, 0}, {1, 1}, {2, 0}
+        };
+        double[] time = {
+                0, 5, 10
+        };
+        int[] mode = {
+                ArcCurveFit.ARC_ABOVE,
+                ArcCurveFit.ARC_ABOVE,
+
+        };
+        CurveFit spline = CurveFit.getArc(mode, time, points);
+        System.out.println("");
+        for (int i = 0; i < time.length; i++) {
+            assertEquals(points[i][0], spline.getPos(time[i], 0), 0.001);
+            assertEquals(points[i][1], spline.getPos(time[i], 1), 0.001);
+        }
+        int count = 50;
+        float dt = (float) (time[time.length - 1] - time[0]) / count - 0.0001f;
+        float[] xp = new float[count];
+        float[] yp = new float[count];
+        for (int i = 0; i < xp.length; i++) {
+            double p = time[0] + i * dt;
+            xp[i] = (float) spline.getPos(p, 0);
+            yp[i] = (float) spline.getPos(p, 1);
+        }
+        String expect = ""
+                + "|*****                    *****| 0.0\n"
+                + "|     **                **     |\n"
+                + "|       **            **       |\n"
+                + "|        *            *        |\n"
+                + "|         *          *         |\n"
+                + "|          *        *          | 0.263\n"
+                + "|           *      **          |\n"
+                + "|            *    *            |\n"
+                + "|            *    *            |\n"
+                + "|             *  *             |\n"
+                + "|             *  *             | 0.526\n"
+                + "|                              |\n"
+                + "|             *  *             |\n"
+                + "|              **              |\n"
+                + "|              **              |\n"
+                + "|              **              | 0.789\n"
+                + "|              **              |\n"
+                + "|              **              |\n"
+                + "|                              |\n"
+                + "|              *               | 0.999\n"
+                + "0.0                        1.936\n";
+        assertEquals(expect, textDraw(30, 20, xp, yp, false));
+        assertEquals(0, spline.getSlope(time[0] + 0.0001, 1), 0.001);
+        assertEquals(0, spline.getSlope(time[1] - 0.01, 0), 0.001);
+        assertEquals(0, spline.getSlope(time[1] + 0.01, 0), 0.001);
+        double dx = spline.getSlope((time[0] + time[1]) / 2, 0);
+        double dy = spline.getSlope((time[0] + time[1]) / 2, 1);
+        assertEquals(1, dx / dy, 0.001);
+        double x = spline.getPos((time[0] + time[1]) / 2, 1);
+        double y = spline.getPos((time[0] + time[1]) / 2, 0);
+        assertEquals(1 - Math.sqrt(0.5), x, 0.001);
+        assertEquals(Math.sqrt(0.5), y, 0.001);
+    }
+
+
+    private static String textDraw(int dimx, int dimy, float[] x, float[] y, boolean flip) {
+        float minX = x[0], maxX = x[0], minY = y[0], maxY = y[0];
+        String ret = "";
+        for (int i = 0; i < x.length; i++) {
+            minX = Math.min(minX, x[i]);
+            maxX = Math.max(maxX, x[i]);
+            minY = Math.min(minY, y[i]);
+            maxY = Math.max(maxY, y[i]);
+        }
+        char[][] c = new char[dimy][dimx];
+        for (int i = 0; i < dimy; i++) {
+            Arrays.fill(c[i], ' ');
+        }
+        int dimx1 = dimx - 1;
+        int dimy1 = dimy - 1;
+        for (int j = 0; j < x.length; j++) {
+            int xp = (int) (dimx1 * (x[j] - minX) / (maxX - minX));
+            int yp = (int) (dimy1 * (y[j] - minY) / (maxY - minY));
+
+            c[flip ? dimy - yp - 1 : yp][xp] = '*';
+        }
+
+        for (int i = 0; i < c.length; i++) {
+            float v;
+            if (flip) {
+                v = (minY - maxY) * (i / (c.length - 1.0f)) + maxY;
+            } else {
+                v = (maxY - minY) * (i / (c.length - 1.0f)) + minY;
+            }
+            v = ((int) (v * 1000 + 0.5)) / 1000.f;
+            if (i % 5 == 0 || i == c.length - 1) {
+                ret += "|" + new String(c[i]) + "| " + v + "\n";
+            } else {
+                ret += "|" + new String(c[i]) + "|\n";
+            }
+        }
+        String minStr = Float.toString(((int) (minX * 1000 + 0.5)) / 1000.f);
+        String maxStr = Float.toString(((int) (maxX * 1000 + 0.5)) / 1000.f);
+        String s = minStr + new String(new char[dimx]).replace('\0', ' ');
+        s = s.substring(0, dimx - maxStr.length() + 2) + maxStr + "\n";
+        return ret + s;
+    }
 }
diff --git a/constraintlayout/constraintlayout/api/current.txt b/constraintlayout/constraintlayout/api/current.txt
index 5a48848..469ddb0 100644
--- a/constraintlayout/constraintlayout/api/current.txt
+++ b/constraintlayout/constraintlayout/api/current.txt
@@ -388,6 +388,7 @@
     field public static final String PERCENT_Y = "percentY";
     field public static final String SIZE_PERCENT = "sizePercent";
     field public static final String TRANSITION_EASING = "transitionEasing";
+    field public static final int TYPE_AXIS = 3; // 0x3
     field public static final int TYPE_CARTESIAN = 0; // 0x0
     field public static final int TYPE_PATH = 1; // 0x1
     field public static final int TYPE_SCREEN = 2; // 0x2
@@ -1106,7 +1107,7 @@
     method protected void setSelfDimensionBehaviour(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, int, int, int, int);
     method public void setState(int, int, int);
     field public static final int DESIGN_INFO_ID = 0; // 0x0
-    field public static final String VERSION = "ConstraintLayout-2.2.0-alpha03";
+    field public static final String VERSION = "ConstraintLayout-2.2.0-alpha04";
     field protected androidx.constraintlayout.widget.ConstraintLayoutStates! mConstraintLayoutSpec;
     field protected boolean mDirtyHierarchy;
     field protected androidx.constraintlayout.core.widgets.ConstraintWidgetContainer! mLayoutWidget;
diff --git a/constraintlayout/constraintlayout/api/public_plus_experimental_current.txt b/constraintlayout/constraintlayout/api/public_plus_experimental_current.txt
index 5a48848..469ddb0 100644
--- a/constraintlayout/constraintlayout/api/public_plus_experimental_current.txt
+++ b/constraintlayout/constraintlayout/api/public_plus_experimental_current.txt
@@ -388,6 +388,7 @@
     field public static final String PERCENT_Y = "percentY";
     field public static final String SIZE_PERCENT = "sizePercent";
     field public static final String TRANSITION_EASING = "transitionEasing";
+    field public static final int TYPE_AXIS = 3; // 0x3
     field public static final int TYPE_CARTESIAN = 0; // 0x0
     field public static final int TYPE_PATH = 1; // 0x1
     field public static final int TYPE_SCREEN = 2; // 0x2
@@ -1106,7 +1107,7 @@
     method protected void setSelfDimensionBehaviour(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, int, int, int, int);
     method public void setState(int, int, int);
     field public static final int DESIGN_INFO_ID = 0; // 0x0
-    field public static final String VERSION = "ConstraintLayout-2.2.0-alpha03";
+    field public static final String VERSION = "ConstraintLayout-2.2.0-alpha04";
     field protected androidx.constraintlayout.widget.ConstraintLayoutStates! mConstraintLayoutSpec;
     field protected boolean mDirtyHierarchy;
     field protected androidx.constraintlayout.core.widgets.ConstraintWidgetContainer! mLayoutWidget;
diff --git a/constraintlayout/constraintlayout/api/restricted_current.txt b/constraintlayout/constraintlayout/api/restricted_current.txt
index 5a48848..469ddb0 100644
--- a/constraintlayout/constraintlayout/api/restricted_current.txt
+++ b/constraintlayout/constraintlayout/api/restricted_current.txt
@@ -388,6 +388,7 @@
     field public static final String PERCENT_Y = "percentY";
     field public static final String SIZE_PERCENT = "sizePercent";
     field public static final String TRANSITION_EASING = "transitionEasing";
+    field public static final int TYPE_AXIS = 3; // 0x3
     field public static final int TYPE_CARTESIAN = 0; // 0x0
     field public static final int TYPE_PATH = 1; // 0x1
     field public static final int TYPE_SCREEN = 2; // 0x2
@@ -1106,7 +1107,7 @@
     method protected void setSelfDimensionBehaviour(androidx.constraintlayout.core.widgets.ConstraintWidgetContainer!, int, int, int, int);
     method public void setState(int, int, int);
     field public static final int DESIGN_INFO_ID = 0; // 0x0
-    field public static final String VERSION = "ConstraintLayout-2.2.0-alpha03";
+    field public static final String VERSION = "ConstraintLayout-2.2.0-alpha04";
     field protected androidx.constraintlayout.widget.ConstraintLayoutStates! mConstraintLayoutSpec;
     field protected boolean mDirtyHierarchy;
     field protected androidx.constraintlayout.core.widgets.ConstraintWidgetContainer! mLayoutWidget;
diff --git a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/KeyPosition.java b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/KeyPosition.java
index ad5ea0c..252d04e 100644
--- a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/KeyPosition.java
+++ b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/KeyPosition.java
@@ -51,6 +51,7 @@
     float mPercentY = Float.NaN;
     float mAltPercentX = Float.NaN;
     float mAltPercentY = Float.NaN;
+    public static final int TYPE_AXIS = 3;
     public static final int TYPE_SCREEN = 2;
     public static final int TYPE_PATH = 1;
     public static final int TYPE_CARTESIAN = 0;
@@ -159,6 +160,9 @@
             case TYPE_SCREEN:
                 positionScreenAttributes(view, start, end, x, y, attribute, value);
                 return;
+            case TYPE_AXIS:
+                positionAxisAttributes(start, end, x, y, attribute, value);
+                return;
             case TYPE_CARTESIAN:
             default:
                 positionCartAttributes(start, end, x, y, attribute, value);
@@ -265,6 +269,44 @@
         }
     }
 
+    void positionAxisAttributes(RectF start,
+                                RectF end,
+                                float x,
+                                float y,
+                                String[] attribute,
+                                float[] value) {
+        float startCenterX = start.centerX();
+        float startCenterY = start.centerY();
+        float endCenterX = end.centerX();
+        float endCenterY = end.centerY();
+        if (startCenterX > endCenterX) {
+            float tmp = startCenterX;
+            startCenterX = endCenterX;
+            endCenterX = tmp;
+        }
+        if (startCenterY > endCenterY) {
+            float tmp = startCenterY;
+            startCenterY = endCenterY;
+            endCenterY = tmp;
+        }
+        float pathVectorX = endCenterX - startCenterX;
+        float pathVectorY = endCenterY - startCenterY;
+        if (attribute[0] != null) { // they are saying what to use
+            if (PERCENT_X.equals(attribute[0])) {
+                value[0] = (x - startCenterX) / pathVectorX;
+                value[1] = (y - startCenterY) / pathVectorY;
+            } else {
+                value[1] = (x - startCenterX) / pathVectorX;
+                value[0] = (y - startCenterY) / pathVectorY;
+            }
+        } else { // we will use what we want to
+            attribute[0] = PERCENT_X;
+            value[0] = (x - startCenterX) / pathVectorX;
+            attribute[1] = PERCENT_Y;
+            value[1] = (y - startCenterY) / pathVectorY;
+        }
+    }
+
     @Override
     public boolean intersects(int layoutWidth,
                               int layoutHeight,
diff --git a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/MotionPaths.java b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/MotionPaths.java
index 92495e0..b695b5b 100644
--- a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/MotionPaths.java
+++ b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/MotionPaths.java
@@ -121,6 +121,63 @@
     }
 
     /**
+     * set up with Axis Relative 0,0 = top,left of bounding rectangle of start and end
+     *
+     * @param c
+     * @param startTimePoint
+     * @param endTimePoint
+     */
+    void initAxis(KeyPosition c, MotionPaths startTimePoint, MotionPaths endTimePoint) {
+        float position = c.mFramePosition / 100f;
+        MotionPaths point = this;
+        point.mTime = position;
+
+        mDrawPath = c.mDrawPath;
+        float scaleWidth = Float.isNaN(c.mPercentWidth) ? position : c.mPercentWidth;
+        float scaleHeight = Float.isNaN(c.mPercentHeight) ? position : c.mPercentHeight;
+        float scaleX = endTimePoint.mWidth - startTimePoint.mWidth;
+        float scaleY = endTimePoint.mHeight - startTimePoint.mHeight;
+
+        point.mPosition = point.mTime;
+
+        float path = position; // the position on the path
+
+        float startCenterX = startTimePoint.mX + startTimePoint.mWidth / 2;
+        float startCenterY = startTimePoint.mY + startTimePoint.mHeight / 2;
+        float endCenterX = endTimePoint.mX + endTimePoint.mWidth / 2;
+        float endCenterY = endTimePoint.mY + endTimePoint.mHeight / 2;
+        if (startCenterX > endCenterX) {
+            float tmp = startCenterX;
+            startCenterX = endCenterX;
+            endCenterX = tmp;
+        }
+        if (startCenterY > endCenterY) {
+            float tmp = startCenterY;
+            startCenterY = endCenterY;
+            endCenterY = tmp;
+        }
+        float pathVectorX = endCenterX - startCenterX;
+        float pathVectorY = endCenterY - startCenterY;
+        point.mX = (int) (startTimePoint.mX + pathVectorX * path - scaleX * scaleWidth / 2);
+        point.mY = (int) (startTimePoint.mY + pathVectorY * path - scaleY * scaleHeight / 2);
+        point.mWidth = (int) (startTimePoint.mWidth + scaleX * scaleWidth);
+        point.mHeight = (int) (startTimePoint.mHeight + scaleY * scaleHeight);
+
+        float dxdx = Float.isNaN(c.mPercentX) ? position : c.mPercentX;
+        float dydx = Float.isNaN(c.mAltPercentY) ? 0 : c.mAltPercentY;
+        float dydy = Float.isNaN(c.mPercentY) ? position : c.mPercentY;
+        float dxdy = Float.isNaN(c.mAltPercentX) ? 0 : c.mAltPercentX;
+        point.mMode = MotionPaths.CARTESIAN;
+        point.mX = (int) (startTimePoint.mX + pathVectorX * dxdx + pathVectorY * dxdy
+                - scaleX * scaleWidth / 2);
+        point.mY = (int) (startTimePoint.mY + pathVectorX * dydx + pathVectorY * dydy
+                - scaleY * scaleHeight / 2);
+
+        point.mKeyFrameEasing = Easing.getInterpolator(c.mTransitionEasing);
+        point.mPathMotionArc = c.mPathMotionArc;
+    }
+
+    /**
      * takes the new keyPosition
      *
      * @param c
@@ -143,6 +200,9 @@
             case KeyPosition.TYPE_PATH:
                 initPath(c, startTimePoint, endTimePoint);
                 return;
+            case KeyPosition.TYPE_AXIS:
+                initAxis(c, startTimePoint, endTimePoint);
+                return;
             default:
             case KeyPosition.TYPE_CARTESIAN:
                 initCartesian(c, startTimePoint, endTimePoint);
diff --git a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintLayout.java b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintLayout.java
index d1dae87..1c94351 100644
--- a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintLayout.java
+++ b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintLayout.java
@@ -562,7 +562,7 @@
     /**
      *
      */
-    public static final String VERSION = "ConstraintLayout-2.2.0-alpha03";
+    public static final String VERSION = "ConstraintLayout-2.2.0-alpha04";
     private static final String TAG = "ConstraintLayout";
 
     private static final boolean USE_CONSTRAINTS_HELPER = true;
diff --git a/constraintlayout/constraintlayout/src/main/res/values/attrs.xml b/constraintlayout/constraintlayout/src/main/res/values/attrs.xml
index c610941..d9029b17 100644
--- a/constraintlayout/constraintlayout/src/main/res/values/attrs.xml
+++ b/constraintlayout/constraintlayout/src/main/res/values/attrs.xml
@@ -642,6 +642,8 @@
         <enum name="startVertical" value="1" />
         <enum name="startHorizontal" value="2" />
         <enum name="flip" value="3" />
+        <enum name="below" value="4" />
+        <enum name="above" value="5" />
     </attr>
 
     <attr name="polarRelativeTo" format="reference" />
@@ -1423,6 +1425,7 @@
             <enum name="deltaRelative" value="0" />
             <enum name="pathRelative" value="1" />
             <enum name="parentRelative" value="2" />
+            <enum name="axisRelative" value="3" />
         </attr>
 
         <!--  Percent distance from start to end along X axis (deltaRelative)
diff --git a/core/core-ktx/src/main/java/androidx/core/os/OutcomeReceiver.kt b/core/core-ktx/src/main/java/androidx/core/os/OutcomeReceiver.kt
index 61ded5a..74052a3 100644
--- a/core/core-ktx/src/main/java/androidx/core/os/OutcomeReceiver.kt
+++ b/core/core-ktx/src/main/java/androidx/core/os/OutcomeReceiver.kt
@@ -61,8 +61,7 @@
 private class ContinuationOutcomeReceiver<R, E : Throwable>(
     private val continuation: Continuation<R>
 ) : OutcomeReceiver<R, E>, AtomicBoolean(false) {
-    @Suppress("WRONG_TYPE_PARAMETER_NULLABILITY_FOR_JAVA_OVERRIDE")
-    override fun onResult(result: R) {
+    override fun onResult(result: R & Any) {
         // Do not attempt to resume more than once, even if the caller of the returned
         // OutcomeReceiver is buggy and tries anyway.
         if (compareAndSet(false, true)) {
diff --git a/core/core/src/androidTest/java/androidx/core/graphics/drawable/WrappedDrawableApi14Test.java b/core/core/src/androidTest/java/androidx/core/graphics/drawable/WrappedDrawableApi14Test.java
index c45c598..dc2b172 100644
--- a/core/core/src/androidTest/java/androidx/core/graphics/drawable/WrappedDrawableApi14Test.java
+++ b/core/core/src/androidTest/java/androidx/core/graphics/drawable/WrappedDrawableApi14Test.java
@@ -16,22 +16,23 @@
 
 package androidx.core.graphics.drawable;
 
-import static org.mockito.Mockito.verify;
+import static org.junit.Assert.assertEquals;
 
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.view.View;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
 import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mockito;
+
+import java.util.concurrent.atomic.AtomicInteger;
 
 @RunWith(AndroidJUnit4.class)
-@MediumTest
+@SmallTest
 public class WrappedDrawableApi14Test {
 
     /**
@@ -40,11 +41,19 @@
     @SdkSuppress(minSdkVersion = 23)
     @Test
     public void testSetLayoutDirection() {
-        // Note that Mockito is VERY SLOW on CF targets, so this test must be medium+.
-        Drawable baseDrawable = Mockito.spy(new ColorDrawable());
+        AtomicInteger layoutDirectionChangedTo = new AtomicInteger(-1);
+
+        Drawable baseDrawable = new ColorDrawable() {
+            @Override
+            public boolean onLayoutDirectionChanged(int layoutDirection) {
+                layoutDirectionChangedTo.set(layoutDirection);
+                return super.onLayoutDirectionChanged(layoutDirection);
+            }
+        };
         WrappedDrawableApi14 drawable = new WrappedDrawableApi14(baseDrawable);
+        drawable.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
         drawable.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
 
-        verify(baseDrawable).setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
+        assertEquals(layoutDirectionChangedTo.get(), View.LAYOUT_DIRECTION_LTR);
     }
 }
diff --git a/credentials/credentials/build.gradle b/credentials/credentials/build.gradle
index ef5161a..e93d59b 100644
--- a/credentials/credentials/build.gradle
+++ b/credentials/credentials/build.gradle
@@ -15,7 +15,6 @@
  */
 
 import androidx.build.LibraryType
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 plugins {
     id("AndroidXPlugin")
diff --git a/datastore/datastore-core/build.gradle b/datastore/datastore-core/build.gradle
index 71aa61d..e534a6b 100644
--- a/datastore/datastore-core/build.gradle
+++ b/datastore/datastore-core/build.gradle
@@ -101,6 +101,9 @@
                 implementation(libs.kotlinTest)
                 implementation(project(":internal-testutils-kmp"))
                 implementation(project(":internal-testutils-datastore"))
+
+                // Workaround bug in 1.8.0, was supposed be fixed in RC2/final, but apparently not.
+                implementation(libs.kotlinTestJunit)
             }
         }
 
@@ -112,6 +115,9 @@
                 implementation(project(":internal-testutils-truth"))
                 implementation(libs.testRunner)
                 implementation(libs.testCore)
+
+                // Workaround bug in 1.8.0, was supposed be fixed in RC2/final, but apparently not.
+                implementation(libs.kotlinTestJunit)
             }
         }
 
diff --git a/datastore/datastore-core/src/androidMain/kotlin/androidx/datastore/core/MultiProcessDataStore.kt b/datastore/datastore-core/src/androidMain/kotlin/androidx/datastore/core/MultiProcessDataStore.kt
index add408e..735bad81 100644
--- a/datastore/datastore-core/src/androidMain/kotlin/androidx/datastore/core/MultiProcessDataStore.kt
+++ b/datastore/datastore-core/src/androidMain/kotlin/androidx/datastore/core/MultiProcessDataStore.kt
@@ -78,8 +78,13 @@
          * Final. ReadException can transition to another ReadException, Data or Final.
          * Data can transition to another Data or Final. Final will not change.
          */
-        // TODO(b/241290444): avoid coroutine switching by loading native lib during initialization
-        val latestVersionAtRead = withContext(scope.coroutineContext) { sharedCounter.getValue() }
+        // Only switch coroutine if sharedCounter is not initialized because initialization incurs
+        // disk IO
+        val latestVersionAtRead =
+            if (lazySharedCounter.isInitialized()) sharedCounter.getValue() else
+                withContext(scope.coroutineContext) {
+                    sharedCounter.getValue()
+                }
         val currentDownStreamFlowState = downstreamFlow.value
 
         if ((currentDownStreamFlowState !is Data) ||
@@ -138,7 +143,8 @@
     private val INVALID_VERSION = -1
     private var initTasks: List<suspend (api: InitializerApi<T>) -> Unit>? =
         initTasksList.toList()
-    private val sharedCounter: SharedCounter by lazy {
+
+    private val lazySharedCounter = lazy {
         SharedCounter.loadLib()
         SharedCounter.create {
             val versionFile = fileWithSuffix(VERSION_SUFFIX)
@@ -146,6 +152,8 @@
             versionFile
         }
     }
+    private val sharedCounter by lazySharedCounter
+
     private val threadLock = Mutex()
     private val storageConnection: StorageConnection<T> by lazy {
         storage.createConnection()
@@ -502,7 +510,8 @@
                         lock = lockFileStream.getChannel().tryLock(
                             /* position= */ 0L,
                             /* size= */ Long.MAX_VALUE,
-                            /* shared= */ true)
+                            /* shared= */ true
+                        )
                     } catch (ex: IOException) {
                         // TODO(b/255419657): Update the shared lock IOException handling logic for
                         // KMM.
diff --git a/datastore/datastore-core/src/jvmTest/kotlin/androidx/datastore/core/SingleProcessDataStoreStressTest.kt b/datastore/datastore-core/src/jvmTest/kotlin/androidx/datastore/core/SingleProcessDataStoreStressTest.kt
index ad91e3b..a4f5183 100644
--- a/datastore/datastore-core/src/jvmTest/kotlin/androidx/datastore/core/SingleProcessDataStoreStressTest.kt
+++ b/datastore/datastore-core/src/jvmTest/kotlin/androidx/datastore/core/SingleProcessDataStoreStressTest.kt
@@ -27,7 +27,7 @@
 import kotlin.test.Ignore
 import kotlin.test.Test
 import kotlin.time.ExperimentalTime
-import kotlin.time.seconds
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.Job
diff --git a/datastore/datastore-rxjava3/src/main/java/androidx/datastore/rxjava3/RxSharedPreferencesMigration.kt b/datastore/datastore-rxjava3/src/main/java/androidx/datastore/rxjava3/RxSharedPreferencesMigration.kt
index b159b92..c7cb50e 100644
--- a/datastore/datastore-rxjava3/src/main/java/androidx/datastore/rxjava3/RxSharedPreferencesMigration.kt
+++ b/datastore/datastore-rxjava3/src/main/java/androidx/datastore/rxjava3/RxSharedPreferencesMigration.kt
@@ -54,7 +54,7 @@
      * @param currentData the most recently persisted data
      * @return a Single of the updated data
      */
-    @Suppress("UPPER_BOUND_VIOLATED_BASED_ON_JAVA_ANNOTATIONS")
+    @Suppress("UPPER_BOUND_VIOLATED")
     public fun migrate(sharedPreferencesView: SharedPreferencesView, currentData: T): Single<T>
 }
 
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 3273ce5..b864500 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -76,7 +76,7 @@
 [0-9]+ problem.* found storing the configuration cache.*
 plus [0-9]+ more problems\. Please see the report for details\.
 # https://youtrack.jetbrains.com/issue/KT-43293 fixed in Kotlin 1.8.0
-\- Task \`((\:[a-z1-9-]+)+)?\:[a-zA-Z1-9]+\` of type \`org\.jetbrains(\.[a-zA-Z]+)+\.(CInteropCommonizerTask|KotlinNativeCompile|KotlinNativeLink|KotlinNativeHostTest|CInteropMetadataDependencyTransformationTask|GenerateProjectStructureMetadata|TransformKotlinGranularMetadata|NativeDistributionCommonizerTask)\`\: invocation of \'Task\.project\' at execution time is unsupported\.
+\- Task \`((\:[a-z1-9-]+)+)?\:[a-zA-Z1-9]+\` of type \`org\.jetbrains(\.[a-zA-Z]+)+\.(CInteropCommonizerTask|KotlinNativeCompile|KotlinNativeLink|KotlinNativeHostTest|CInteropMetadataDependencyTransformationTask|GenerateProjectStructureMetadata|TransformKotlinGranularMetadata|NativeDistributionCommonizerTask|transformNonJvmMainDependenciesMetadata|MetadataDependencyTransformationTask)\`\: invocation of \'Task\.project\' at execution time is unsupported\.
 \- Task \`((\:[a-z1-9-]+)+)?\:[a-zA-Z1-9]+\` of type \`org\.jetbrains\.kotlin\.gradle\.targets\.native\.internal\.(CInteropMetadataDependencyTransformationTask|NativeDistributionCommonizerTask)\`: cannot serialize object of type \'org\.gradle(\.[a-zA-Z]+)+\'\, a subtype of \'org\.gradle(\.[a-zA-Z]+)+\'\, as these are not supported with the configuration cache\.
 # https://youtrack.jetbrains.com/issue/KT-54627
 \- Task \`\:commonizeNativeDistribution\` of type \`org\.jetbrains\.kotlin\.gradle\.targets\.native\.internal\.NativeDistributionCommonizerTask\`\: error writing value of type \'java\.util\.concurrent\.locks\.ReentrantLock\'
@@ -706,6 +706,21 @@
 WARN: Missing @param tag for parameter `clipOp` of function androidx\.compose\.ui\.graphics/Canvas/clipRect/\#kotlin\.Float\#kotlin\.Float\#kotlin\.Float\#kotlin\.Float\#androidx\.compose\.ui\.graphics\.ClipOp/PointingToDeclaration/
 WARN: Missing @param tag for parameter `paint` of function androidx\.compose\.ui\.graphics/Canvas/drawArc/\#kotlin\.Float\#kotlin\.Float\#kotlin\.Float\#kotlin\.Float\#kotlin\.Float\#kotlin\.Float\#kotlin\.Boolean\#androidx\.compose\.ui\.graphics\.Paint/PointingToDeclaration/
 WARN: Missing @param tag for parameter `operation` of function androidx\.compose\.ui\.graphics/AndroidPath/op/\#androidx\.compose\.ui\.graphics\.Path\#androidx\.compose\.ui\.graphics\.Path\#androidx\.compose\.ui\.graphics\.PathOperation/PointingToDeclaration/
+WARN\: Multiple sources exist for RestrictTo\. Artifact ID metadata will not be displayed
+WARN\: Multiple sources exist for Scope\. Artifact ID metadata will not be displayed
+WARN\: Multiple sources exist for SparseArrayCompat\. Artifact ID metadata will not be displayed
+WARN\: Multiple sources exist for LongSparseArray\. Artifact ID metadata will not be displayed
+WARNING: link to @throws type IOException does not resolve\. Is it from a package that the containing file does not import\? Is docs inherited to an un\-documented override function, but the exception class is not in scope in the inheriting class\? The general fix for these is to fully qualify the exception name,  e\.g\.`@throws java\.io\.IOException under some conditions\. This was observed in Throws\(root=CustomDocTag\(children=\[P\(children=\[Text\(body=Unrecoverable IO exception when trying to access the underlying storage\., children=\[\], params=\{\}\)\], params=\{\}\)\], params=\{\}, name=MARKDOWN_FILE\), name=IOException, exceptionAddress=null\)\.`
+WARN: Multiple sources exist for PreferencesSerializer\. Artifact ID metadata will not be displayed
+WARN: Multiple sources exist for PreferenceDataStoreFactory\. Artifact ID metadata will not be displayed
+WARN: Multiple sources exist for DataStoreFactory\. Artifact ID metadata will not be displayed
+WARN: Multiple sources exist for ReplaceFileCorruptionHandler\. Artifact ID metadata will not be displayed
+WARNING\: link to \@throws type ArrayIndexOutOfBoundsException does not resolve\. Is it from a package that the containing file does not import\? Is docs inherited to an un\-documented override function\, but the exception class is not in scope in the inheriting class\? The general fix for these is to fully qualify the exception name\,  e\.g\.\`\@throws java\.io\.IOException under some conditions\. This was observed in Throws\(root\=CustomDocTag\(children\=\[P\(children\=\[Text\(body\=if \, children\=\[\]\, params\=\{\}\)\, DocumentationLink\(dri\=androidx\.collection\/CircularArray\/\/\/PointingToDeclaration\/\, children\=\[Text\(body\=CircularArray\, children\=\[\]\, params\=\{\}\)\]\, params\=\{href\=\[CircularArray\]\}\)\, Text\(body\= is empty\, children\=\[\]\, params\=\{\}\)\]\, params\=\{\}\)\]\, params\=\{\}\, name\=MARKDOWN_FILE\)\, name\=ArrayIndexOutOfBoundsException\, exceptionAddress\=null\)\.\`
+WARNING\: link to \@throws type ArrayIndexOutOfBoundsException does not resolve\. Is it from a package that the containing file does not import\? Is docs inherited to an un\-documented override function\, but the exception class is not in scope in the inheriting class\? The general fix for these is to fully qualify the exception name\,  e\.g\.\`\@throws java\.io\.IOException under some conditions\. This was observed in Throws\(root\=CustomDocTag\(children\=\[P\(children\=\[Text\(body\=if n \, children\=\[\]\, params\=\{\}\)\, Text\(body\=\<\, children\=\[\]\, params\=\{content\-type\=html\}\)\, Text\(body\= [0-9]+ or n \, children\=\[\]\, params\=\{\}\)\, Text\(body\=\>\, children\=\[\]\, params\=\{content\-type\=html\}\)\, Text\(body\=\= size\(\)\, children\=\[\]\, params\=\{\}\)\]\, params\=\{\}\)\]\, params\=\{\}\, name\=MARKDOWN_FILE\)\, name\=ArrayIndexOutOfBoundsException\, exceptionAddress\=null\)\.\`
+WARNING\: link to \@throws type ArrayIndexOutOfBoundsException does not resolve\. Is it from a package that the containing file does not import\? Is docs inherited to an un\-documented override function\, but the exception class is not in scope in the inheriting class\? The general fix for these is to fully qualify the exception name\,  e\.g\.\`\@throws java\.io\.IOException under some conditions\. This was observed in Throws\(root\=CustomDocTag\(children\=\[P\(children\=\[Text\(body\=if \, children\=\[\]\, params\=\{\}\)\, DocumentationLink\(dri\=androidx\.collection\/CircularArray\/\/\/PointingToDeclaration\/\, children\=\[Text\(body\=CircularArray\, children\=\[\]\, params\=\{\}\)\]\, params\=\{href\=\[CircularArray\]\}\)\, Text\(body\= is empty \(on jvm\)\, children\=\[\]\, params\=\{\}\)\]\, params\=\{\}\)\]\, params\=\{\}\, name\=MARKDOWN_FILE\)\, name\=ArrayIndexOutOfBoundsException\, exceptionAddress\=null\)\.\`
+WARNING\: link to \@throws type ArrayIndexOutOfBoundsException does not resolve\. Is it from a package that the containing file does not import\? Is docs inherited to an un\-documented override function\, but the exception class is not in scope in the inheriting class\? The general fix for these is to fully qualify the exception name\,  e\.g\.\`\@throws java\.io\.IOException under some conditions\. This was observed in Throws\(root\=CustomDocTag\(children\=\[P\(children\=\[Text\(body\=if \, children\=\[\]\, params\=\{\}\)\, DocumentationLink\(dri\=androidx\.collection\/CircularArray\/removeFromEnd\/\#kotlin\.Int\/PointingToCallableParameters\([0-9]+\)\/\, children\=\[Text\(body\=count\, children\=\[\]\, params\=\{\}\)\]\, params\=\{href\=\[count\]\}\)\, Text\(body\= is larger than \, children\=\[\]\, params\=\{\}\)\, DocumentationLink\(dri\=androidx\.collection\/CircularArray\/size\/\#\/PointingToDeclaration\/\, children\=\[Text\(body\=size\, children\=\[\]\, params\=\{\}\)\]\, params\=\{href\=\[size\]\}\)\]\, params\=\{\}\)\]\, params\=\{\}\, name\=MARKDOWN_FILE\)\, name\=ArrayIndexOutOfBoundsException\, exceptionAddress\=null\)\.\`
+WARNING\: link to \@throws type ArrayIndexOutOfBoundsException does not resolve\. Is it from a package that the containing file does not import\? Is docs inherited to an un\-documented override function\, but the exception class is not in scope in the inheriting class\? The general fix for these is to fully qualify the exception name\,  e\.g\.\`\@throws java\.io\.IOException under some conditions\. This was observed in Throws\(root\=CustomDocTag\(children\=\[P\(children\=\[Text\(body\=if \, children\=\[\]\, params\=\{\}\)\, DocumentationLink\(dri\=androidx\.collection\/CircularArray\/removeFromStart\/\#kotlin\.Int\/PointingToCallableParameters\([0-9]+\)\/\, children\=\[Text\(body\=count\, children\=\[\]\, params\=\{\}\)\]\, params\=\{href\=\[count\]\}\)\, Text\(body\= is larger than \, children\=\[\]\, params\=\{\}\)\, DocumentationLink\(dri\=androidx\.collection\/CircularArray\/size\/\#\/PointingToDeclaration\/\, children\=\[Text\(body\=size\, children\=\[\]\, params\=\{\}\)\]\, params\=\{href\=\[size\]\}\)\]\, params\=\{\}\)\]\, params\=\{\}\, name\=MARKDOWN_FILE\)\, name\=ArrayIndexOutOfBoundsException\, exceptionAddress\=null\)\.\`
+WARN\: Multiple sources exist for ArraySet\. Artifact ID metadata will not be displayed
 # Wire proto generation, task :generateDebugProtos
 Writing .* to \$OUT_DIR/.*/build/generated/source/wire
 # > Task :compose:ui:ui-tooling:processDebugAndroidTestManifest
diff --git a/development/checkstyle/.gitignore b/development/checkstyle/.gitignore
deleted file mode 100644
index d163863..0000000
--- a/development/checkstyle/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-build/
\ No newline at end of file
diff --git a/development/checkstyle/LICENSE b/development/checkstyle/LICENSE
deleted file mode 100644
index c1f5472..0000000
--- a/development/checkstyle/LICENSE
+++ /dev/null
@@ -1,502 +0,0 @@
-		  GNU LESSER GENERAL PUBLIC LICENSE
-		       Version 2.1, February 1999
-
- Copyright (C) 1991, 1999 Free Software Foundation, Inc.
-     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-[This is the first released version of the Lesser GPL.  It also counts
- as the successor of the GNU Library Public License, version 2, hence
- the version number 2.1.]
-
-			    Preamble
-
-  The licenses for most software are designed to take away your
-freedom to share and change it.  By contrast, the GNU General Public
-Licenses are intended to guarantee your freedom to share and change
-free software--to make sure the software is free for all its users.
-
-  This license, the Lesser General Public License, applies to some
-specially designated software packages--typically libraries--of the
-Free Software Foundation and other authors who decide to use it.  You
-can use it too, but we suggest you first think carefully about whether
-this license or the ordinary General Public License is the better
-strategy to use in any particular case, based on the explanations below.
-
-  When we speak of free software, we are referring to freedom of use,
-not price.  Our General Public Licenses are designed to make sure that
-you have the freedom to distribute copies of free software (and charge
-for this service if you wish); that you receive source code or can get
-it if you want it; that you can change the software and use pieces of
-it in new free programs; and that you are informed that you can do
-these things.
-
-  To protect your rights, we need to make restrictions that forbid
-distributors to deny you these rights or to ask you to surrender these
-rights.  These restrictions translate to certain responsibilities for
-you if you distribute copies of the library or if you modify it.
-
-  For example, if you distribute copies of the library, whether gratis
-or for a fee, you must give the recipients all the rights that we gave
-you.  You must make sure that they, too, receive or can get the source
-code.  If you link other code with the library, you must provide
-complete object files to the recipients, so that they can relink them
-with the library after making changes to the library and recompiling
-it.  And you must show them these terms so they know their rights.
-
-  We protect your rights with a two-step method: (1) we copyright the
-library, and (2) we offer you this license, which gives you legal
-permission to copy, distribute and/or modify the library.
-
-  To protect each distributor, we want to make it very clear that
-there is no warranty for the free library.  Also, if the library is
-modified by someone else and passed on, the recipients should know
-that what they have is not the original version, so that the original
-author's reputation will not be affected by problems that might be
-introduced by others.
-
-  Finally, software patents pose a constant threat to the existence of
-any free program.  We wish to make sure that a company cannot
-effectively restrict the users of a free program by obtaining a
-restrictive license from a patent holder.  Therefore, we insist that
-any patent license obtained for a version of the library must be
-consistent with the full freedom of use specified in this license.
-
-  Most GNU software, including some libraries, is covered by the
-ordinary GNU General Public License.  This license, the GNU Lesser
-General Public License, applies to certain designated libraries, and
-is quite different from the ordinary General Public License.  We use
-this license for certain libraries in order to permit linking those
-libraries into non-free programs.
-
-  When a program is linked with a library, whether statically or using
-a shared library, the combination of the two is legally speaking a
-combined work, a derivative of the original library.  The ordinary
-General Public License therefore permits such linking only if the
-entire combination fits its criteria of freedom.  The Lesser General
-Public License permits more lax criteria for linking other code with
-the library.
-
-  We call this license the "Lesser" General Public License because it
-does Less to protect the user's freedom than the ordinary General
-Public License.  It also provides other free software developers Less
-of an advantage over competing non-free programs.  These disadvantages
-are the reason we use the ordinary General Public License for many
-libraries.  However, the Lesser license provides advantages in certain
-special circumstances.
-
-  For example, on rare occasions, there may be a special need to
-encourage the widest possible use of a certain library, so that it becomes
-a de-facto standard.  To achieve this, non-free programs must be
-allowed to use the library.  A more frequent case is that a free
-library does the same job as widely used non-free libraries.  In this
-case, there is little to gain by limiting the free library to free
-software only, so we use the Lesser General Public License.
-
-  In other cases, permission to use a particular library in non-free
-programs enables a greater number of people to use a large body of
-free software.  For example, permission to use the GNU C Library in
-non-free programs enables many more people to use the whole GNU
-operating system, as well as its variant, the GNU/Linux operating
-system.
-
-  Although the Lesser General Public License is Less protective of the
-users' freedom, it does ensure that the user of a program that is
-linked with the Library has the freedom and the wherewithal to run
-that program using a modified version of the Library.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.  Pay close attention to the difference between a
-"work based on the library" and a "work that uses the library".  The
-former contains code derived from the library, whereas the latter must
-be combined with the library in order to run.
-
-		  GNU LESSER GENERAL PUBLIC LICENSE
-   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
-  0. This License Agreement applies to any software library or other
-program which contains a notice placed by the copyright holder or
-other authorized party saying it may be distributed under the terms of
-this Lesser General Public License (also called "this License").
-Each licensee is addressed as "you".
-
-  A "library" means a collection of software functions and/or data
-prepared so as to be conveniently linked with application programs
-(which use some of those functions and data) to form executables.
-
-  The "Library", below, refers to any such software library or work
-which has been distributed under these terms.  A "work based on the
-Library" means either the Library or any derivative work under
-copyright law: that is to say, a work containing the Library or a
-portion of it, either verbatim or with modifications and/or translated
-straightforwardly into another language.  (Hereinafter, translation is
-included without limitation in the term "modification".)
-
-  "Source code" for a work means the preferred form of the work for
-making modifications to it.  For a library, complete source code means
-all the source code for all modules it contains, plus any associated
-interface definition files, plus the scripts used to control compilation
-and installation of the library.
-
-  Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope.  The act of
-running a program using the Library is not restricted, and output from
-such a program is covered only if its contents constitute a work based
-on the Library (independent of the use of the Library in a tool for
-writing it).  Whether that is true depends on what the Library does
-and what the program that uses the Library does.
-
-  1. You may copy and distribute verbatim copies of the Library's
-complete source code as you receive it, in any medium, provided that
-you conspicuously and appropriately publish on each copy an
-appropriate copyright notice and disclaimer of warranty; keep intact
-all the notices that refer to this License and to the absence of any
-warranty; and distribute a copy of this License along with the
-Library.
-
-  You may charge a fee for the physical act of transferring a copy,
-and you may at your option offer warranty protection in exchange for a
-fee.
-
-  2. You may modify your copy or copies of the Library or any portion
-of it, thus forming a work based on the Library, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
-    a) The modified work must itself be a software library.
-
-    b) You must cause the files modified to carry prominent notices
-    stating that you changed the files and the date of any change.
-
-    c) You must cause the whole of the work to be licensed at no
-    charge to all third parties under the terms of this License.
-
-    d) If a facility in the modified Library refers to a function or a
-    table of data to be supplied by an application program that uses
-    the facility, other than as an argument passed when the facility
-    is invoked, then you must make a good faith effort to ensure that,
-    in the event an application does not supply such function or
-    table, the facility still operates, and performs whatever part of
-    its purpose remains meaningful.
-
-    (For example, a function in a library to compute square roots has
-    a purpose that is entirely well-defined independent of the
-    application.  Therefore, Subsection 2d requires that any
-    application-supplied function or table used by this function must
-    be optional: if the application does not supply it, the square
-    root function must still compute square roots.)
-
-These requirements apply to the modified work as a whole.  If
-identifiable sections of that work are not derived from the Library,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works.  But when you
-distribute the same sections as part of a whole which is a work based
-on the Library, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote
-it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Library.
-
-In addition, mere aggregation of another work not based on the Library
-with the Library (or with a work based on the Library) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
-  3. You may opt to apply the terms of the ordinary GNU General Public
-License instead of this License to a given copy of the Library.  To do
-this, you must alter all the notices that refer to this License, so
-that they refer to the ordinary GNU General Public License, version 2,
-instead of to this License.  (If a newer version than version 2 of the
-ordinary GNU General Public License has appeared, then you can specify
-that version instead if you wish.)  Do not make any other change in
-these notices.
-
-  Once this change is made in a given copy, it is irreversible for
-that copy, so the ordinary GNU General Public License applies to all
-subsequent copies and derivative works made from that copy.
-
-  This option is useful when you wish to copy part of the code of
-the Library into a program that is not a library.
-
-  4. You may copy and distribute the Library (or a portion or
-derivative of it, under Section 2) in object code or executable form
-under the terms of Sections 1 and 2 above provided that you accompany
-it with the complete corresponding machine-readable source code, which
-must be distributed under the terms of Sections 1 and 2 above on a
-medium customarily used for software interchange.
-
-  If distribution of object code is made by offering access to copy
-from a designated place, then offering equivalent access to copy the
-source code from the same place satisfies the requirement to
-distribute the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
-  5. A program that contains no derivative of any portion of the
-Library, but is designed to work with the Library by being compiled or
-linked with it, is called a "work that uses the Library".  Such a
-work, in isolation, is not a derivative work of the Library, and
-therefore falls outside the scope of this License.
-
-  However, linking a "work that uses the Library" with the Library
-creates an executable that is a derivative of the Library (because it
-contains portions of the Library), rather than a "work that uses the
-library".  The executable is therefore covered by this License.
-Section 6 states terms for distribution of such executables.
-
-  When a "work that uses the Library" uses material from a header file
-that is part of the Library, the object code for the work may be a
-derivative work of the Library even though the source code is not.
-Whether this is true is especially significant if the work can be
-linked without the Library, or if the work is itself a library.  The
-threshold for this to be true is not precisely defined by law.
-
-  If such an object file uses only numerical parameters, data
-structure layouts and accessors, and small macros and small inline
-functions (ten lines or less in length), then the use of the object
-file is unrestricted, regardless of whether it is legally a derivative
-work.  (Executables containing this object code plus portions of the
-Library will still fall under Section 6.)
-
-  Otherwise, if the work is a derivative of the Library, you may
-distribute the object code for the work under the terms of Section 6.
-Any executables containing that work also fall under Section 6,
-whether or not they are linked directly with the Library itself.
-
-  6. As an exception to the Sections above, you may also combine or
-link a "work that uses the Library" with the Library to produce a
-work containing portions of the Library, and distribute that work
-under terms of your choice, provided that the terms permit
-modification of the work for the customer's own use and reverse
-engineering for debugging such modifications.
-
-  You must give prominent notice with each copy of the work that the
-Library is used in it and that the Library and its use are covered by
-this License.  You must supply a copy of this License.  If the work
-during execution displays copyright notices, you must include the
-copyright notice for the Library among them, as well as a reference
-directing the user to the copy of this License.  Also, you must do one
-of these things:
-
-    a) Accompany the work with the complete corresponding
-    machine-readable source code for the Library including whatever
-    changes were used in the work (which must be distributed under
-    Sections 1 and 2 above); and, if the work is an executable linked
-    with the Library, with the complete machine-readable "work that
-    uses the Library", as object code and/or source code, so that the
-    user can modify the Library and then relink to produce a modified
-    executable containing the modified Library.  (It is understood
-    that the user who changes the contents of definitions files in the
-    Library will not necessarily be able to recompile the application
-    to use the modified definitions.)
-
-    b) Use a suitable shared library mechanism for linking with the
-    Library.  A suitable mechanism is one that (1) uses at run time a
-    copy of the library already present on the user's computer system,
-    rather than copying library functions into the executable, and (2)
-    will operate properly with a modified version of the library, if
-    the user installs one, as long as the modified version is
-    interface-compatible with the version that the work was made with.
-
-    c) Accompany the work with a written offer, valid for at
-    least three years, to give the same user the materials
-    specified in Subsection 6a, above, for a charge no more
-    than the cost of performing this distribution.
-
-    d) If distribution of the work is made by offering access to copy
-    from a designated place, offer equivalent access to copy the above
-    specified materials from the same place.
-
-    e) Verify that the user has already received a copy of these
-    materials or that you have already sent this user a copy.
-
-  For an executable, the required form of the "work that uses the
-Library" must include any data and utility programs needed for
-reproducing the executable from it.  However, as a special exception,
-the materials to be distributed need not include anything that is
-normally distributed (in either source or binary form) with the major
-components (compiler, kernel, and so on) of the operating system on
-which the executable runs, unless that component itself accompanies
-the executable.
-
-  It may happen that this requirement contradicts the license
-restrictions of other proprietary libraries that do not normally
-accompany the operating system.  Such a contradiction means you cannot
-use both them and the Library together in an executable that you
-distribute.
-
-  7. You may place library facilities that are a work based on the
-Library side-by-side in a single library together with other library
-facilities not covered by this License, and distribute such a combined
-library, provided that the separate distribution of the work based on
-the Library and of the other library facilities is otherwise
-permitted, and provided that you do these two things:
-
-    a) Accompany the combined library with a copy of the same work
-    based on the Library, uncombined with any other library
-    facilities.  This must be distributed under the terms of the
-    Sections above.
-
-    b) Give prominent notice with the combined library of the fact
-    that part of it is a work based on the Library, and explaining
-    where to find the accompanying uncombined form of the same work.
-
-  8. You may not copy, modify, sublicense, link with, or distribute
-the Library except as expressly provided under this License.  Any
-attempt otherwise to copy, modify, sublicense, link with, or
-distribute the Library is void, and will automatically terminate your
-rights under this License.  However, parties who have received copies,
-or rights, from you under this License will not have their licenses
-terminated so long as such parties remain in full compliance.
-
-  9. You are not required to accept this License, since you have not
-signed it.  However, nothing else grants you permission to modify or
-distribute the Library or its derivative works.  These actions are
-prohibited by law if you do not accept this License.  Therefore, by
-modifying or distributing the Library (or any work based on the
-Library), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Library or works based on it.
-
-  10. Each time you redistribute the Library (or any work based on the
-Library), the recipient automatically receives a license from the
-original licensor to copy, distribute, link with or modify the Library
-subject to these terms and conditions.  You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties with
-this License.
-
-  11. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Library at all.  For example, if a patent
-license would not permit royalty-free redistribution of the Library by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Library.
-
-If any portion of this section is held invalid or unenforceable under any
-particular circumstance, the balance of the section is intended to apply,
-and the section as a whole is intended to apply in other circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system which is
-implemented by public license practices.  Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
-  12. If the distribution and/or use of the Library is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Library under this License may add
-an explicit geographical distribution limitation excluding those countries,
-so that distribution is permitted only in or among countries not thus
-excluded.  In such case, this License incorporates the limitation as if
-written in the body of this License.
-
-  13. The Free Software Foundation may publish revised and/or new
-versions of the Lesser General Public License from time to time.
-Such new versions will be similar in spirit to the present version,
-but may differ in detail to address new problems or concerns.
-
-Each version is given a distinguishing version number.  If the Library
-specifies a version number of this License which applies to it and
-"any later version", you have the option of following the terms and
-conditions either of that version or of any later version published by
-the Free Software Foundation.  If the Library does not specify a
-license version number, you may choose any version ever published by
-the Free Software Foundation.
-
-  14. If you wish to incorporate parts of the Library into other free
-programs whose distribution conditions are incompatible with these,
-write to the author to ask for permission.  For software which is
-copyrighted by the Free Software Foundation, write to the Free
-Software Foundation; we sometimes make exceptions for this.  Our
-decision will be guided by the two goals of preserving the free status
-of all derivatives of our free software and of promoting the sharing
-and reuse of software generally.
-
-			    NO WARRANTY
-
-  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
-WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
-EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
-OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
-KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
-LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
-THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
-WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
-AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
-FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
-CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
-LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
-RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
-FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
-SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
-DAMAGES.
-
-		     END OF TERMS AND CONDITIONS
-
-           How to Apply These Terms to Your New Libraries
-
-  If you develop a new library, and you want it to be of the greatest
-possible use to the public, we recommend making it free software that
-everyone can redistribute and change.  You can do so by permitting
-redistribution under these terms (or, alternatively, under the terms of the
-ordinary General Public License).
-
-  To apply these terms, attach the following notices to the library.  It is
-safest to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least the
-"copyright" line and a pointer to where the full notice is found.
-
-    <one line to give the library's name and a brief idea of what it does.>
-    Copyright (C) <year>  <name of author>
-
-    This library is free software; you can redistribute it and/or
-    modify it under the terms of the GNU Lesser General Public
-    License as published by the Free Software Foundation; either
-    version 2.1 of the License, or (at your option) any later version.
-
-    This library is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-    Lesser General Public License for more details.
-
-    You should have received a copy of the GNU Lesser General Public
-    License along with this library; if not, write to the Free Software
-    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-Also add information on how to contact you by electronic and paper mail.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the library, if
-necessary.  Here is a sample; alter the names:
-
-  Yoyodyne, Inc., hereby disclaims all copyright interest in the
-  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
-
-  <signature of Ty Coon>, 1 April 1990
-  Ty Coon, President of Vice
-
-That's all there is to it!
diff --git a/development/checkstyle/README b/development/checkstyle/README
deleted file mode 100644
index 8f9bf38..0000000
--- a/development/checkstyle/README
+++ /dev/null
@@ -1,16 +0,0 @@
-Description:
-Checkstyle is used by developers to validate Java code style before running
-repo upload. This project implements Checkstyle checks specific to the Android
-support library.
-
-Projects used:
-* Name: Checkstyle
-  Description: Checkstyle is a development tool to help programmers write Java
-               code that adheres to a coding standard.
-  URL: http://checkstyle.sourceforge.net/
-  Version: 6.12.1
-  License: LGPL 2.1
-  License File: LICENSE
-  Local Modifications:
-  - The only source file used here is MissingDeprecatedCheck, which was adapted
-    to create MissingRestrictToCheck.
diff --git a/development/checkstyle/build.gradle b/development/checkstyle/build.gradle
deleted file mode 100644
index 1121887..0000000
--- a/development/checkstyle/build.gradle
+++ /dev/null
@@ -1,20 +0,0 @@
-apply plugin: "java"
-
-compileJava {
-    sourceCompatibility = JavaVersion.VERSION_1_7
-    targetCompatibility = JavaVersion.VERSION_1_7
-}
-
-dependencies {
-    compile files("../../../../prebuilts/checkstyle/checkstyle.jar")
-}
-
-sourceSets {
-    main.java.srcDir "src"
-}
-
-jar {
-    from sourceSets.main.output
-    baseName = "com.android.support.checkstyle"
-    destinationDir = new File("prebuilt")
-}
diff --git a/development/checkstyle/config/support-lib.xml b/development/checkstyle/config/support-lib.xml
deleted file mode 100644
index 49ecc5c..0000000
--- a/development/checkstyle/config/support-lib.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ~ Copyright (C) 2016 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.
--->
-<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN" "http://www.puppycrawl.com/dtds/configuration_1_3.dtd" [
-    <!ENTITY defaultCopyrightCheck SYSTEM "../../../../../prebuilts/checkstyle/default-copyright-check.xml">
-    <!ENTITY defaultJavadocChecks SYSTEM "../../../../../prebuilts/checkstyle/default-javadoc-checks.xml">
-    <!ENTITY defaultTreewalkerChecks SYSTEM "../../../../../prebuilts/checkstyle/default-treewalker-checks.xml">
-    <!ENTITY defaultModuleChecks SYSTEM "../../../../../prebuilts/checkstyle/default-module-checks.xml">
-    ]>
-
-<module name="Checker">
-    &defaultModuleChecks;
-    &defaultCopyrightCheck;
-    <module name="TreeWalker">
-        &defaultJavadocChecks;
-        &defaultTreewalkerChecks;
-
-        <module name="RedundantModifierCheck" />
-
-        <!-- Custom support library check for @RestrictTo / @hide. -->
-        <module name="com.android.support.checkstyle.MismatchedAnnotationCheck">
-            <property name="severity" value="error" />
-            <property name="tag" value="hide" />
-            <property name="annotation" value="androidx.annotation.RestrictTo" />
-            <property name="messageKey" value="annotation.missing.hide" />
-            <message key="annotation.missing.hide" value="Must include both @RestrictTo annotation and @hide Javadoc tag."/>
-        </module>
-    </module>
-</module>
diff --git a/development/checkstyle/gradle/wrapper/gradle-wrapper.jar b/development/checkstyle/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 13372ae..0000000
--- a/development/checkstyle/gradle/wrapper/gradle-wrapper.jar
+++ /dev/null
Binary files differ
diff --git a/development/checkstyle/gradle/wrapper/gradle-wrapper.properties b/development/checkstyle/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index cb1b272..0000000
--- a/development/checkstyle/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-#Tue Aug 16 10:43:36 PDT 2016
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=../../../../../../tools/external/gradle/gradle-4.1-bin.zip
diff --git a/development/checkstyle/gradlew b/development/checkstyle/gradlew
deleted file mode 100755
index 9d82f78..0000000
--- a/development/checkstyle/gradlew
+++ /dev/null
@@ -1,160 +0,0 @@
-#!/usr/bin/env bash
-
-##############################################################################
-##
-##  Gradle start up script for UN*X
-##
-##############################################################################
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
-    echo "$*"
-}
-
-die ( ) {
-    echo
-    echo "$*"
-    echo
-    exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-case "`uname`" in
-  CYGWIN* )
-    cygwin=true
-    ;;
-  Darwin* )
-    darwin=true
-    ;;
-  MINGW* )
-    msys=true
-    ;;
-esac
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
-    ls=`ls -ld "$PRG"`
-    link=`expr "$ls" : '.*-> \(.*\)$'`
-    if expr "$link" : '/.*' > /dev/null; then
-        PRG="$link"
-    else
-        PRG=`dirname "$PRG"`"/$link"
-    fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
-    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
-        # IBM's JDK on AIX uses strange locations for the executables
-        JAVACMD="$JAVA_HOME/jre/sh/java"
-    else
-        JAVACMD="$JAVA_HOME/bin/java"
-    fi
-    if [ ! -x "$JAVACMD" ] ; then
-        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-    fi
-else
-    JAVACMD="java"
-    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
-    MAX_FD_LIMIT=`ulimit -H -n`
-    if [ $? -eq 0 ] ; then
-        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
-            MAX_FD="$MAX_FD_LIMIT"
-        fi
-        ulimit -n $MAX_FD
-        if [ $? -ne 0 ] ; then
-            warn "Could not set maximum file descriptor limit: $MAX_FD"
-        fi
-    else
-        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
-    fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
-    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
-    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
-    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-    JAVACMD=`cygpath --unix "$JAVACMD"`
-
-    # We build the pattern for arguments to be converted via cygpath
-    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
-    SEP=""
-    for dir in $ROOTDIRSRAW ; do
-        ROOTDIRS="$ROOTDIRS$SEP$dir"
-        SEP="|"
-    done
-    OURCYGPATTERN="(^($ROOTDIRS))"
-    # Add a user-defined pattern to the cygpath arguments
-    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
-        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
-    fi
-    # Now convert the arguments - kludge to limit ourselves to /bin/sh
-    i=0
-    for arg in "$@" ; do
-        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
-        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
-
-        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
-            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
-        else
-            eval `echo args$i`="\"$arg\""
-        fi
-        i=$((i+1))
-    done
-    case $i in
-        (0) set -- ;;
-        (1) set -- "$args0" ;;
-        (2) set -- "$args0" "$args1" ;;
-        (3) set -- "$args0" "$args1" "$args2" ;;
-        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
-        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
-        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
-        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
-        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
-        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
-    esac
-fi
-
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
-    JVM_OPTS=("$@")
-}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
-
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/development/checkstyle/prebuilt/com.android.support.checkstyle.jar b/development/checkstyle/prebuilt/com.android.support.checkstyle.jar
deleted file mode 100644
index c59a474..0000000
--- a/development/checkstyle/prebuilt/com.android.support.checkstyle.jar
+++ /dev/null
Binary files differ
diff --git a/development/checkstyle/src/com/android/support/checkstyle/MismatchedAnnotationCheck.java b/development/checkstyle/src/com/android/support/checkstyle/MismatchedAnnotationCheck.java
deleted file mode 100644
index a1a60dff..0000000
--- a/development/checkstyle/src/com/android/support/checkstyle/MismatchedAnnotationCheck.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * checkstyle: Checks Java source code for adherence to a set of rules.
- * Copyright (C) 2001-2016 the original author or authors.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-package com.android.support.checkstyle;
-
-import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
-import com.puppycrawl.tools.checkstyle.api.DetailAST;
-import com.puppycrawl.tools.checkstyle.api.TextBlock;
-import com.puppycrawl.tools.checkstyle.api.TokenTypes;
-import com.puppycrawl.tools.checkstyle.utils.AnnotationUtility;
-import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
-
-import java.util.regex.Pattern;
-
-/**
- * This class is used to verify that both an annotation and javadoc tag are
- * present when either one is present.
- * <p>
- * Typically, both ways of flagging APIs serve their own purposes. Annotations
- * are used for compilers and development tools, while javadoc tags are used
- * for documentation.
- * <p>
- * In some cases, the presence of an annotations implies the presence of a
- * javadoc tag (or vice versa). For example, in the case of the
- * {@literal @}Deprecated annotation, the {@literal @}deprecated tag should
- * also be present. In the case of the {@literal @}RestrictTo tag, the
- * {@literal @}hide tag should also be present.
- * <p>
- * To configure this check, do the following:
- * <pre>
- *     &lt;module name="MismatchedAnnotationCheck"&gt;
- *       &lt;property name="tag" value="hide" /&gt;
- *       &lt;property name="annotation" value="android.support.annotation.RestrictTo" /&gt;
- *       &lt;property name="messageKey" value="annotation.missing.hide" /&gt;
- *       &lt;message key="annotation.missing.hide"
- *                   value="Must include both {@literal @}RestrictTo annotation
- *                          and {@literal @}hide Javadoc tag." /&gt;
- *     &lt;/module&gt;
- * </pre>
- */
-@SuppressWarnings("unused")
-public final class MismatchedAnnotationCheck extends AbstractCheck {
-
-    /** Key for the warning message text by check properties. */
-    private static final String MSG_KEY_JAVADOC_DUPLICATE_TAG = "javadoc.duplicateTag";
-
-    /** Key for the warning message text by check properties. */
-    private static final String MSG_KEY_JAVADOC_MISSING = "javadoc.missing";
-
-    /** Javadoc tag. */
-    private String mTag;
-
-    /** Pattern for matching javadoc tag. */
-    private Pattern mMatchTag;
-
-    /** Simple annotation name. */
-    private String mAnnotationSimpleName;
-
-    /** Fully-qualified annotation name. */
-    private String mAnnotation;
-
-    /** Key for the warning message text specified by check properties. */
-    private String mMessageKey;
-
-    @Override
-    public int[] getDefaultTokens() {
-        return getAcceptableTokens();
-    }
-
-    /**
-     * Sets javadoc tag.
-     *
-     * @param tag javadoc tag to check
-     */
-    @SuppressWarnings("unused")
-    public void setTag(String tag) {
-        mTag = tag;
-
-        // Tag may either have a description or be on a line by itself.
-        mMatchTag = CommonUtils.createPattern("@" + tag + "(?:\\s|$)");
-    }
-
-    /**
-     * Sets annotation tag.
-     *
-     * @param annotation annotation to check
-     */
-    @SuppressWarnings("unused")
-    public void setAnnotation(String annotation) {
-        mAnnotation = annotation;
-
-        // Extract the simple class name.
-        final int lastDollar = annotation.lastIndexOf('$');
-        final int lastSep = lastDollar >= 0 ? lastDollar : annotation.lastIndexOf('.');
-        mAnnotationSimpleName = annotation.substring(lastSep + 1);
-    }
-
-
-    /**
-     * Sets annotation tag.
-     *
-     * @param messageKey key to use for failed check message
-     */
-    @SuppressWarnings("unused")
-    public void setMessageKey(String messageKey) {
-        mMessageKey = messageKey;
-    }
-
-    @Override
-    public int[] getAcceptableTokens() {
-        return new int[] {
-                TokenTypes.INTERFACE_DEF,
-                TokenTypes.CLASS_DEF,
-                TokenTypes.ANNOTATION_DEF,
-                TokenTypes.ENUM_DEF,
-                TokenTypes.METHOD_DEF,
-                TokenTypes.CTOR_DEF,
-                TokenTypes.VARIABLE_DEF,
-                TokenTypes.ENUM_CONSTANT_DEF,
-                TokenTypes.ANNOTATION_FIELD_DEF,
-        };
-    }
-
-    @Override
-    public int[] getRequiredTokens() {
-        return getAcceptableTokens();
-    }
-
-    @Override
-    public void visitToken(final DetailAST ast) {
-        final boolean containsAnnotation =
-                AnnotationUtility.containsAnnotation(ast, mAnnotationSimpleName)
-                        || AnnotationUtility.containsAnnotation(ast, mAnnotation);
-        final boolean containsJavadocTag = containsJavadocTag(ast);
-        if (containsAnnotation ^ containsJavadocTag) {
-            log(ast.getLineNo(), mMessageKey);
-        }
-    }
-
-    /**
-     * Checks to see if the text block contains the tag.
-     *
-     * @param ast the AST being visited
-     * @return true if contains the tag
-     */
-    private boolean containsJavadocTag(final DetailAST ast) {
-        final TextBlock javadoc = getFileContents().getJavadocBefore(ast.getLineNo());
-        if (javadoc == null) {
-            return false;
-        }
-
-        int currentLine = javadoc.getStartLineNo();
-        boolean found = false;
-
-        final String[] lines = javadoc.getText();
-        for (String line : lines) {
-            if (mMatchTag.matcher(line).find()) {
-                if (found) {
-                    log(currentLine, MSG_KEY_JAVADOC_DUPLICATE_TAG, mTag);
-                }
-                found = true;
-            }
-            currentLine++;
-        }
-
-        return found;
-    }
-}
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 9ebbebc..f8d7b91 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -16,7 +16,7 @@
     docs(project(":ads:ads-identifier-common"))
     docs(project(":ads:ads-identifier-provider"))
     docs(project(":ads:ads-identifier-testing"))
-    docs(project(":annotation:annotation"))
+    kmpDocs(project(":annotation:annotation"))
     docs(project(":annotation:annotation-experimental"))
     docs(project(":appactions:interaction:interaction-proto"))
     docs(project(":appcompat:appcompat"))
@@ -56,7 +56,7 @@
     docs(project(":car:app:app-projected"))
     docs(project(":car:app:app-testing"))
     docs(project(":cardview:cardview"))
-    docs(project(":collection:collection"))
+    kmpDocs(project(":collection:collection"))
     docs(project(":collection:collection-ktx"))
     docs(project(":compose:animation:animation"))
     docs(project(":compose:animation:animation-core"))
@@ -134,9 +134,10 @@
     docs(project(":customview:customview"))
     docs(project(":customview:customview-poolingcontainer"))
     docs(project(":datastore:datastore"))
-    docs(project(":datastore:datastore-core"))
+    kmpDocs(project(":datastore:datastore-core"))
+    kmpDocs(project(":datastore:datastore-core-okio"))
     docs(project(":datastore:datastore-preferences"))
-    docs(project(":datastore:datastore-preferences-core"))
+    kmpDocs(project(":datastore:datastore-preferences-core"))
     docs(project(":datastore:datastore-preferences-proto"))
     docs(project(":datastore:datastore-preferences-rxjava2"))
     docs(project(":datastore:datastore-preferences-rxjava3"))
diff --git a/emoji2/emoji2-emojipicker/build.gradle b/emoji2/emoji2-emojipicker/build.gradle
index 17422f1..be8163d 100644
--- a/emoji2/emoji2-emojipicker/build.gradle
+++ b/emoji2/emoji2-emojipicker/build.gradle
@@ -24,6 +24,7 @@
 
 dependencies {
     api(libs.kotlinStdlib)
+    implementation(libs.constraintLayout)
     implementation(libs.kotlinCoroutinesCore)
     implementation("androidx.core:core-ktx:1.8.0")
     implementation project(path: ':emoji2:emoji2')
diff --git a/emoji2/emoji2-emojipicker/samples/src/main/AndroidManifest.xml b/emoji2/emoji2-emojipicker/samples/src/main/AndroidManifest.xml
index 141611d..a155641 100644
--- a/emoji2/emoji2-emojipicker/samples/src/main/AndroidManifest.xml
+++ b/emoji2/emoji2-emojipicker/samples/src/main/AndroidManifest.xml
@@ -15,12 +15,11 @@
   limitations under the License.
   -->
 
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
     <uses-sdk android:minSdkVersion="21"/>
     <application>
         <activity android:name=".MainActivity" android:exported="true"
-            android:theme="@style/Theme.AppCompat.DayNight">
+            android:theme="@style/MyPinkTheme">
             <!-- Handle Google app icon launch. -->
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
diff --git a/emoji2/emoji2-emojipicker/samples/src/main/java/androidx/emoji2/emojipicker/samples/MainActivity.kt b/emoji2/emoji2-emojipicker/samples/src/main/java/androidx/emoji2/emojipicker/samples/MainActivity.kt
index dde4446..1447129 100644
--- a/emoji2/emoji2-emojipicker/samples/src/main/java/androidx/emoji2/emojipicker/samples/MainActivity.kt
+++ b/emoji2/emoji2-emojipicker/samples/src/main/java/androidx/emoji2/emojipicker/samples/MainActivity.kt
@@ -18,8 +18,8 @@
 
 import android.content.Context
 import android.os.Bundle
-import android.widget.Button
 import android.widget.EditText
+import android.widget.ToggleButton
 import androidx.appcompat.app.AppCompatActivity
 import androidx.emoji2.emojipicker.EmojiPickerView
 import androidx.emoji2.emojipicker.RecentEmojiProvider
@@ -35,10 +35,16 @@
         }
         view.setRecentEmojiProvider(CustomRecentEmojiProvider(applicationContext))
 
-        findViewById<Button>(R.id.button).setOnClickListener {
-            view.emojiGridColumns = 8
-            view.emojiGridRows = 8.3f
-        }
+        findViewById<ToggleButton>(R.id.button)
+            .setOnCheckedChangeListener { _, isChecked ->
+                if (isChecked) {
+                    view.emojiGridColumns = 8
+                    view.emojiGridRows = 8.3f
+                } else {
+                    view.emojiGridColumns = 9
+                    view.emojiGridRows = 15f
+                }
+            }
     }
 }
 
diff --git a/emoji2/emoji2-emojipicker/samples/src/main/res/layout-land/main.xml b/emoji2/emoji2-emojipicker/samples/src/main/res/layout-land/main.xml
index e55d888..41230cb 100644
--- a/emoji2/emoji2-emojipicker/samples/src/main/res/layout-land/main.xml
+++ b/emoji2/emoji2-emojipicker/samples/src/main/res/layout-land/main.xml
@@ -25,11 +25,12 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content" />
 
-    <Button
+    <ToggleButton
         android:id="@+id/button"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:text="Change Layout" />
+        android:textOff="Display Larger Emojis"
+        android:textOn="Display Smaller Emojis" />
 
     <androidx.emoji2.emojipicker.EmojiPickerView
         android:id="@+id/emoji_picker"
diff --git a/emoji2/emoji2-emojipicker/samples/src/main/res/layout/main.xml b/emoji2/emoji2-emojipicker/samples/src/main/res/layout/main.xml
index 2270f92..fc424b5 100644
--- a/emoji2/emoji2-emojipicker/samples/src/main/res/layout/main.xml
+++ b/emoji2/emoji2-emojipicker/samples/src/main/res/layout/main.xml
@@ -25,11 +25,12 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content" />
 
-    <Button
+    <ToggleButton
         android:id="@+id/button"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:text="Change Layout" />
+        android:textOff="Display Larger Emojis"
+        android:textOn="Display Smaller Emojis" />
 
     <androidx.emoji2.emojipicker.EmojiPickerView
         android:id="@+id/emoji_picker"
diff --git a/emoji2/emoji2-emojipicker/samples/src/main/res/values/styles.xml b/emoji2/emoji2-emojipicker/samples/src/main/res/values/styles.xml
new file mode 100644
index 0000000..89ce1c3
--- /dev/null
+++ b/emoji2/emoji2-emojipicker/samples/src/main/res/values/styles.xml
@@ -0,0 +1,31 @@
+
+<!--
+  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.
+  -->
+
+<resources>
+
+    <style name="MyPinkTheme" parent="Theme.AppCompat.DayNight" >
+        <!-- The color for selected headers -->
+        <item name="colorAccent">#696969</item>
+        <!-- The color for unselected headers -->
+        <item name="colorControlNormal">#FFC0CB</item>
+        <!-- The color for all text -->
+        <item name="android:textColorPrimary">#696969</item>
+        <!-- The color for variant popup background -->
+        <item name="colorButtonNormal">#FFC0CB</item>
+    </style>
+
+</resources>
diff --git a/emoji2/emoji2-emojipicker/src/androidTest/java/androidx/emoji2/emojipicker/EmojiPickerViewTest.kt b/emoji2/emoji2-emojipicker/src/androidTest/java/androidx/emoji2/emojipicker/EmojiPickerViewTest.kt
index 96d02de..6244759 100644
--- a/emoji2/emoji2-emojipicker/src/androidTest/java/androidx/emoji2/emojipicker/EmojiPickerViewTest.kt
+++ b/emoji2/emoji2-emojipicker/src/androidTest/java/androidx/emoji2/emojipicker/EmojiPickerViewTest.kt
@@ -24,7 +24,6 @@
 import android.view.View.GONE
 import android.view.View.VISIBLE
 import android.view.ViewGroup
-import android.widget.FrameLayout
 import android.widget.ImageView
 import androidx.core.view.children
 import androidx.core.view.isVisible
@@ -40,6 +39,7 @@
 import androidx.test.espresso.matcher.ViewMatchers.withId
 import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
 import org.hamcrest.Description
@@ -96,7 +96,7 @@
             )!!
             // No variant indicator
             assertEquals(
-                (targetView.parent as FrameLayout).findViewById<ImageView>(
+                (targetView.parent.parent as ViewGroup).findViewById<ImageView>(
                     EmojiPickerViewR.id.variant_availability_indicator
                 ).visibility,
                 GONE
@@ -121,7 +121,7 @@
         val targetView = findViewByEmoji(view, NOSE_EMOJI)!!
         // Variant indicator visible
         assertEquals(
-            (targetView.parent as FrameLayout).findViewById<ImageView>(
+            (targetView.parent.parent as ViewGroup).findViewById<ImageView>(
                 EmojiPickerViewR.id.variant_availability_indicator
             ).visibility, VISIBLE
         )
@@ -188,6 +188,7 @@
         assertSelectedHeaderIndex(4)
     }
 
+    @FlakyTest(bugId = 265006871)
     @Test(expected = UnsupportedOperationException::class)
     fun testAddView_throwsException() {
         activityTestRule.scenario.onActivity {
@@ -227,7 +228,7 @@
             BoundedMatcher<RecyclerView.ViewHolder, EmojiViewHolder>(EmojiViewHolder::class.java) {
             override fun describeTo(description: Description) {}
             override fun matchesSafely(item: EmojiViewHolder) =
-                (item.itemView as FrameLayout)
+                (item.itemView as ViewGroup)
                     .findViewById<EmojiView>(EmojiPickerViewR.id.emoji_view)
                     .emoji == emoji
         }
diff --git a/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/EmojiPickerBodyAdapter.kt b/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/EmojiPickerBodyAdapter.kt
index b18d7a4..21a9b10 100644
--- a/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/EmojiPickerBodyAdapter.kt
+++ b/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/EmojiPickerBodyAdapter.kt
@@ -50,7 +50,7 @@
                 R.layout.empty_category_text_view, parent
             ) {
                 minimumHeight =
-                    emojiCellHeight ?: (parent.measuredHeight / emojiGridRows).toInt()
+                    emojiCellHeight ?: (getEmojiCellTotalHeight(parent) / (emojiGridRows)).toInt()
                         .also { emojiCellHeight = it }
             }
 
@@ -60,7 +60,7 @@
                     emojiCellWidth ?: (getParentWidth(parent) / emojiGridColumns).also {
                         emojiCellWidth = it
                     },
-                    emojiCellHeight ?: (parent.measuredHeight / emojiGridRows).toInt()
+                    emojiCellHeight ?: (getEmojiCellTotalHeight(parent) / (emojiGridRows)).toInt()
                         .also { emojiCellHeight = it },
                     layoutInflater,
                     stickyVariantProvider,
@@ -114,6 +114,13 @@
         return parent.measuredWidth - parent.paddingLeft - parent.paddingRight
     }
 
+    private fun getEmojiCellTotalHeight(parent: ViewGroup) =
+        parent.measuredHeight - context.resources.getDimensionPixelSize(
+            R.dimen.emoji_picker_category_name_height
+        ) * 2 - context.resources.getDimensionPixelSize(
+            R.dimen.emoji_picker_category_name_padding_top
+        )
+
     private fun createSimpleHolder(
         @LayoutRes layoutId: Int,
         parent: ViewGroup,
diff --git a/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/PopupViewHelper.kt b/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/PopupViewHelper.kt
index f6e41bb..8038e48 100644
--- a/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/PopupViewHelper.kt
+++ b/emoji2/emoji2-emojipicker/src/main/java/androidx/emoji2/emojipicker/PopupViewHelper.kt
@@ -7,8 +7,8 @@
 import android.view.LayoutInflater
 import android.view.View
 import android.view.View.OnClickListener
+import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
-import android.widget.FrameLayout
 import android.widget.GridLayout
 import androidx.core.content.ContextCompat
 
@@ -99,8 +99,8 @@
                     R.layout.emoji_view_holder,
                     null,
                     false
-                ) as FrameLayout).apply {
-                    (this.getChildAt(0) as EmojiView).apply {
+                ) as ViewGroup).apply {
+                    findViewById<EmojiView>(R.id.emoji_view).apply {
                         emoji = variants[it - 1]
                         setOnClickListener(clickListener)
                         if (it == 1) {
diff --git a/emoji2/emoji2-emojipicker/src/main/res/drawable/variant_availability_indicator.xml b/emoji2/emoji2-emojipicker/src/main/res/drawable/variant_availability_indicator.xml
index e27fe2e..a471ae6 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/drawable/variant_availability_indicator.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/drawable/variant_availability_indicator.xml
@@ -19,7 +19,7 @@
     android:height="24dp"
     android:viewportWidth="24.0"
     android:viewportHeight="24.0"
-    android:tint="?attr/colorControlNormal">
+    android:tint="?attr/colorButtonNormal">
     <path
         android:fillColor="@android:color/white"
         android:pathData="M2,22h20V2L2,22z"/>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/layout/category_text_view.xml b/emoji2/emoji2-emojipicker/src/main/res/layout/category_text_view.xml
index 2ed8ede..c7bc5af 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/layout/category_text_view.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/layout/category_text_view.xml
@@ -15,17 +15,17 @@
   limitations under the License.
   -->
 
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:importantForAccessibility="no">
     <androidx.appcompat.widget.AppCompatTextView
         android:id="@+id/category_name"
         android:layout_width="wrap_content"
-        android:layout_height="24dp"
+        android:layout_height="@dimen/emoji_picker_category_name_height"
         android:layout_alignParentStart="true"
         android:layout_alignParentTop="true"
-        android:paddingTop="4dp"
+        android:paddingTop="@dimen/emoji_picker_category_name_padding_top"
         android:paddingLeft="7dp"
         android:paddingRight="7dp"
         android:gravity="center_vertical|start"
@@ -33,4 +33,4 @@
         android:importantForAccessibility="yes"
         android:textColor="?android:attr/textColorPrimary"
         android:textSize="12dp" />
-</RelativeLayout>
+</FrameLayout>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/layout/emoji_view_holder.xml b/emoji2/emoji2-emojipicker/src/main/res/layout/emoji_view_holder.xml
index 5c64c9f..eec343d 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/layout/emoji_view_holder.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/layout/emoji_view_holder.xml
@@ -14,24 +14,36 @@
   limitations under the License.
   -->
 
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="0dp"
     android:layout_height="0dp">
 
-    <androidx.emoji2.emojipicker.EmojiView
-        android:id="@+id/emoji_view"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:background="@drawable/ripple_emoji_view"
-        android:importantForAccessibility="yes"
-        android:textSize="30dp"
-        android:layout_margin="1dp" />
+    <FrameLayout
+        android:id="@+id/emoji_view_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" >
+        <androidx.emoji2.emojipicker.EmojiView
+            android:id="@+id/emoji_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="@drawable/ripple_emoji_view"
+            android:importantForAccessibility="yes"
+            android:textSize="30dp"
+            android:layout_margin="1dp" />
+    </FrameLayout>
     <ImageView
         android:id="@+id/variant_availability_indicator"
         android:visibility="gone"
         android:src="@drawable/variant_availability_indicator"
         android:layout_width="5dp"
         android:layout_height="5dp"
-        android:layout_gravity="bottom|end"
-        android:textColor="?android:attr/textColorPrimary" />
-</FrameLayout>
\ No newline at end of file
+        android:textColor="?android:attr/textColorPrimary"
+        app:layout_constraintEnd_toEndOf="@id/emoji_view_frame"
+        app:layout_constraintBottom_toBottomOf="@id/emoji_view_frame"/>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values/dimens.xml b/emoji2/emoji2-emojipicker/src/main/res/values/dimens.xml
index af3fdd2..7c2cd47 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values/dimens.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values/dimens.xml
@@ -32,4 +32,6 @@
     <dimen name="emoji_picker_popup_view_elevation">8dp</dimen>
     <dimen name="emoji_picker_popup_view_holder_corner_radius">30dp</dimen>
     <dimen name="emoji_picker_skin_tone_circle_radius">6dp</dimen>
+    <dimen name="emoji_picker_category_name_height">24dp</dimen>
+    <dimen name="emoji_picker_category_name_padding_top">4dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/fragment/fragment-ktx/api/current.txt b/fragment/fragment-ktx/api/current.txt
index b582114..052abdb 100644
--- a/fragment/fragment-ktx/api/current.txt
+++ b/fragment/fragment-ktx/api/current.txt
@@ -15,9 +15,9 @@
   }
 
   public final class FragmentTransactionKt {
-    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction add(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String tag, optional android.os.Bundle? args);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction add(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String? tag, optional android.os.Bundle? args);
     method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction add(androidx.fragment.app.FragmentTransaction, String tag, optional android.os.Bundle? args);
-    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction replace(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String tag, optional android.os.Bundle? args);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction replace(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String? tag, optional android.os.Bundle? args);
   }
 
   public final class FragmentViewModelLazyKt {
diff --git a/fragment/fragment-ktx/api/public_plus_experimental_current.txt b/fragment/fragment-ktx/api/public_plus_experimental_current.txt
index b582114..052abdb 100644
--- a/fragment/fragment-ktx/api/public_plus_experimental_current.txt
+++ b/fragment/fragment-ktx/api/public_plus_experimental_current.txt
@@ -15,9 +15,9 @@
   }
 
   public final class FragmentTransactionKt {
-    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction add(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String tag, optional android.os.Bundle? args);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction add(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String? tag, optional android.os.Bundle? args);
     method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction add(androidx.fragment.app.FragmentTransaction, String tag, optional android.os.Bundle? args);
-    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction replace(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String tag, optional android.os.Bundle? args);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction replace(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String? tag, optional android.os.Bundle? args);
   }
 
   public final class FragmentViewModelLazyKt {
diff --git a/fragment/fragment-ktx/api/restricted_current.txt b/fragment/fragment-ktx/api/restricted_current.txt
index b582114..052abdb 100644
--- a/fragment/fragment-ktx/api/restricted_current.txt
+++ b/fragment/fragment-ktx/api/restricted_current.txt
@@ -15,9 +15,9 @@
   }
 
   public final class FragmentTransactionKt {
-    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction add(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String tag, optional android.os.Bundle? args);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction add(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String? tag, optional android.os.Bundle? args);
     method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction add(androidx.fragment.app.FragmentTransaction, String tag, optional android.os.Bundle? args);
-    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction replace(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String tag, optional android.os.Bundle? args);
+    method public static inline <reified F extends androidx.fragment.app.Fragment> androidx.fragment.app.FragmentTransaction replace(androidx.fragment.app.FragmentTransaction, @IdRes int containerViewId, optional String? tag, optional android.os.Bundle? args);
   }
 
   public final class FragmentViewModelLazyKt {
diff --git a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverTest.kt b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverTest.kt
index bc53366..809209f 100644
--- a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverTest.kt
+++ b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiverTest.kt
@@ -35,8 +35,10 @@
 import android.widget.TextView
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.DpSize
@@ -98,6 +100,8 @@
 import kotlin.test.assertIs
 import kotlin.test.assertNotNull
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.collectIndexed
 import kotlinx.coroutines.flow.take
@@ -134,7 +138,6 @@
         TestGlanceAppWidget.sizeMode = SizeMode.Single
     }
 
-    @Ignore // b/261993523
     @Test
     fun createSimpleAppWidget() {
         TestGlanceAppWidget.uiDefinition = {
@@ -187,7 +190,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @FlakyTest(bugId = 249803914)
     @Test
     fun createResponsiveAppWidget() {
@@ -226,7 +228,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @Test
     fun createTextWithFillMaxDimensions() {
         TestGlanceAppWidget.uiDefinition = {
@@ -253,7 +254,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @Test
     fun createTextViewWithMixedDimensions() {
         TestGlanceAppWidget.uiDefinition = {
@@ -267,7 +267,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @Test
     fun createBoxWithExactDimensions() {
         TestGlanceAppWidget.uiDefinition = {
@@ -284,7 +283,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @Test
     fun createBoxWithMixedDimensions() {
         TestGlanceAppWidget.uiDefinition = {
@@ -302,7 +300,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @Test
     fun createColumnWithMixedDimensions() {
         TestGlanceAppWidget.uiDefinition = {
@@ -326,7 +323,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @Test
     fun createRowWithMixedDimensions() {
         TestGlanceAppWidget.uiDefinition = {
@@ -350,7 +346,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @Test
     fun createRowWithTwoTexts() {
         TestGlanceAppWidget.uiDefinition = {
@@ -376,7 +371,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @Test
     fun createColumnWithTwoTexts() {
         TestGlanceAppWidget.uiDefinition = {
@@ -402,7 +396,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @Test
     fun createColumnWithTwoTexts2() {
         TestGlanceAppWidget.uiDefinition = {
@@ -428,7 +421,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @Test
     fun createButton() {
         TestGlanceAppWidget.uiDefinition = {
@@ -455,7 +447,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @Test
     fun createImage() {
         TestGlanceAppWidget.uiDefinition = {
@@ -471,7 +462,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @Test
     fun drawableBackground() {
         TestGlanceAppWidget.uiDefinition = {
@@ -511,7 +501,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @Test
     fun bitmapBackground() {
         TestGlanceAppWidget.uiDefinition = {
@@ -538,7 +527,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @Test
     fun removeAppWidget() {
         TestGlanceAppWidget.uiDefinition = {
@@ -580,7 +568,6 @@
             .isFalse()
     }
 
-    @Ignore // b/261993523
     @Test
     fun updateAll() = runTest {
         TestGlanceAppWidget.uiDefinition = {
@@ -594,7 +581,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @Test
     fun updateIf() = runTest {
         val didRun = AtomicBoolean(false)
@@ -646,7 +632,6 @@
         assertThat(didRun.get()).isFalse()
     }
 
-    @Ignore // b/261993523
     @Test
     fun viewState() {
         TestGlanceAppWidget.uiDefinition = {
@@ -672,7 +657,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @Test
     fun actionCallback() {
         TestGlanceAppWidget.uiDefinition = {
@@ -714,7 +698,6 @@
         assertThat(CallbackTest.received.get()).containsExactly(1, 2)
     }
 
-    @Ignore // b/261993523
     @Test
     fun multipleActionCallback() {
         TestGlanceAppWidget.uiDefinition = {
@@ -746,7 +729,6 @@
         assertThat(CallbackTest.received.get()).containsExactly(2)
     }
 
-    @Ignore // b/261993523
     @Test
     fun wrapAroundFillMaxSize() {
         TestGlanceAppWidget.uiDefinition = {
@@ -802,8 +784,7 @@
                     checked = true,
                     onCheckedChange = actionRunCallback<CompoundButtonActionTest>(
                         actionParametersOf(CompoundButtonActionTest.key to switch)
-                    ),
-                    text = switch
+                    ), text = switch
                 )
             }
         }
@@ -864,7 +845,6 @@
         // if no crash, we're good
     }
 
-    @Ignore // b/261993523
     @Test
     fun radioActionCallback() {
         TestGlanceAppWidget.uiDefinition = {
@@ -889,7 +869,6 @@
         assertThat(CallbackTest.received.get()).containsExactly(2)
     }
 
-    @Ignore // b/261993523
     @Test
     fun lambdaActionCallback() = runTest {
         if (!useSessionManager) return@runTest
@@ -918,7 +897,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @FlakyTest(bugId = 259938473)
     @Test
     fun unsetActionCallback() = runTest {
@@ -937,7 +915,6 @@
         }
 
         mHostRule.startHost()
-
         mHostRule.onHostView { root ->
             val view =
                 checkNotNull(
@@ -945,7 +922,6 @@
                 )
             assertThat(view.hasOnClickListeners()).isTrue()
         }
-
         updateAppWidgetState(context, AppWidgetId(mHostRule.appWidgetId)) {
             it[testBoolKey] = false
         }
@@ -962,7 +938,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @Test
     fun unsetCompoundButtonActionCallback() = runTest {
         TestGlanceAppWidget.uiDefinition = {
@@ -1008,7 +983,6 @@
         assertThat(CompoundButtonActionTest.received.get()).isEmpty()
     }
 
-    @Ignore // b/261993523
     @SdkSuppress(minSdkVersion = 31)
     @Test
     fun compoundButtonsOnlyHaveOneAction() {
@@ -1036,7 +1010,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @Test
     fun elementsWithActionsHaveRipples() {
         TestGlanceAppWidget.uiDefinition = {
@@ -1059,7 +1032,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @Test
     fun elementsWithNoActionsDontHaveRipples() {
         TestGlanceAppWidget.uiDefinition = {
@@ -1073,7 +1045,6 @@
         }
     }
 
-    @Ignore // b/261993523
     @SdkSuppress(minSdkVersion = 31)
     @Test
     fun compoundButtonsDoNotHaveRipples() {
@@ -1092,17 +1063,20 @@
         }
     }
 
-    @Ignore // b/261993523
     @Test
-    fun cancellingContentCoroutineMakesContentLeaveComposition() = runBlocking {
-        if (!useSessionManager) return@runBlocking
-
+        fun cancellingContentCoroutineCausesContentToLeaveComposition() = runBlocking {
+            if (!useSessionManager) return@runBlocking
         val currentEffectState = MutableStateFlow(EffectState.Initial)
-        TestGlanceAppWidget.uiDefinition = {
-            DisposableEffect(true) {
-                currentEffectState.tryEmit(EffectState.Started)
-                onDispose {
-                    currentEffectState.tryEmit(EffectState.Disposed)
+        var contentJob: Job? = null
+        TestGlanceAppWidget.onProvideGlance = {
+            coroutineScope {
+                contentJob = launch {
+                    provideContent {
+                        DisposableEffect(true) {
+                            currentEffectState.tryEmit(EffectState.Started)
+                            onDispose { currentEffectState.tryEmit(EffectState.Disposed) }
+                        }
+                    }
                 }
             }
         }
@@ -1112,7 +1086,7 @@
                 0 -> assertThat(state).isEqualTo(EffectState.Initial)
                 1 -> {
                     assertThat(state).isEqualTo(EffectState.Started)
-                    assertNotNull(TestGlanceAppWidget.contentCoroutine.get()).cancel()
+                    assertNotNull(contentJob).cancel()
                 }
                 2 -> assertThat(state).isEqualTo(EffectState.Disposed)
             }
diff --git a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/LazyColumnTest.kt b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/LazyColumnTest.kt
index c383082..feed04a 100644
--- a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/LazyColumnTest.kt
+++ b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/LazyColumnTest.kt
@@ -35,7 +35,6 @@
 import androidx.glance.layout.Alignment
 import androidx.glance.layout.padding
 import androidx.glance.text.Text
-import androidx.test.filters.FlakyTest
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth.assertThat
@@ -69,7 +68,6 @@
         }
     }
 
-    @FlakyTest(bugId = 206481702)
     @Test
     fun item_withoutItemIds_createsNonStableList() {
         TestGlanceAppWidget.uiDefinition = {
@@ -148,7 +146,7 @@
             assertThat(adapter.getItemId(0)).isEqualTo(0L)
             assertThat(adapter.getItemId(1)).isEqualTo(2L)
             assertThat(adapter.getItemId(2)).isEqualTo(4L)
-            assertThat(adapter.getItemId(3)).isEqualTo(ReservedItemIdRangeEnd)
+            assertThat(adapter.getItemId(3)).isEqualTo(ReservedItemIdRangeEnd - 3)
         }
     }
 
diff --git a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/TestGlanceAppWidgetReceiver.kt b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/TestGlanceAppWidgetReceiver.kt
index d2832ae..fb9ff6c 100644
--- a/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/TestGlanceAppWidgetReceiver.kt
+++ b/glance/glance-appwidget/src/androidAndroidTest/kotlin/androidx/glance/appwidget/TestGlanceAppWidgetReceiver.kt
@@ -48,6 +48,17 @@
         uiDefinition()
     }
 
+    override suspend fun provideGlance(
+        context: Context,
+        id: GlanceId
+    ) {
+        onProvideGlance?.invoke(this)
+        onProvideGlance = null
+        provideContent(uiDefinition)
+    }
+
+    var onProvideGlance: (suspend TestGlanceAppWidget.() -> Unit)? = null
+
     private var onDeleteBlock: ((GlanceId) -> Unit)? = null
 
     fun setOnDeleteBlock(block: (GlanceId) -> Unit) {
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/AppWidgetSession.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/AppWidgetSession.kt
index 40ceb00..fa0e117 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/AppWidgetSession.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/AppWidgetSession.kt
@@ -24,12 +24,18 @@
 import androidx.annotation.VisibleForTesting
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.neverEqualPolicy
+import androidx.compose.runtime.produceState
+import androidx.compose.runtime.remember
 import androidx.compose.ui.unit.DpSize
 import androidx.datastore.preferences.core.emptyPreferences
 import androidx.glance.EmittableWithChildren
 import androidx.glance.GlanceComposable
+import androidx.glance.GlanceId
 import androidx.glance.LocalContext
 import androidx.glance.LocalGlanceId
 import androidx.glance.LocalState
@@ -38,9 +44,14 @@
 import androidx.glance.state.ConfigManager
 import androidx.glance.state.GlanceState
 import androidx.glance.state.PreferencesGlanceStateDefinition
-import java.util.concurrent.CancellationException
+import java.util.concurrent.atomic.AtomicReference
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CancellableContinuation
+import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.channelFlow
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
 
 /**
  * A session that composes UI for a single app widget.
@@ -78,34 +89,35 @@
 
     override fun createRootEmittable() = RemoteViewsRoot(MaxComposeTreeDepth)
 
-    override suspend fun provideGlance(
-        context: Context,
-    ): Flow<@Composable @GlanceComposable () -> Unit> {
-        val manager = context.appWidgetManager
-        val minSize = appWidgetMinSize(
-            context.resources.displayMetrics,
-            manager,
-            id.appWidgetId
-        )
-        options.value = initialOptions ?: manager.getAppWidgetOptions(id.appWidgetId)!!
-        glanceState.value =
-            configManager.getValue(context, PreferencesGlanceStateDefinition, key)
-        return widget.runGlance(context, id).map {
-                content: (@Composable @GlanceComposable () -> Unit)? ->
-            {
-                CompositionLocalProvider(
-                    LocalContext provides context,
-                    LocalGlanceId provides id,
-                    LocalAppWidgetOptions provides options.value,
-                    LocalState provides glanceState.value,
-                ) {
-                    if (content != null) {
-                        ForEachSize(widget.sizeMode, minSize, content)
-                    } else {
-                        IgnoreResult()
-                    }
-                }
+    override fun provideGlance(context: Context): @Composable @GlanceComposable () -> Unit = {
+        CompositionLocalProvider(
+            LocalContext provides context,
+            LocalGlanceId provides id,
+            LocalAppWidgetOptions provides options.value,
+            LocalState provides glanceState.value,
+        ) {
+            val manager = remember { context.appWidgetManager }
+            val minSize = remember {
+                appWidgetMinSize(
+                    context.resources.displayMetrics,
+                    manager,
+                    id.appWidgetId
+                )
             }
+            val configIsReady by produceState(false) {
+                options.value = initialOptions ?: manager.getAppWidgetOptions(id.appWidgetId)
+                glanceState.value =
+                    configManager.getValue(context, PreferencesGlanceStateDefinition, key)
+                value = true
+            }
+            remember { widget.runGlance(context, id) }
+                .collectAsState(null)
+                .takeIf { configIsReady }
+                ?.value?.let { ForEachSize(widget.sizeMode, minSize, it) }
+                ?: IgnoreResult()
+            // The following line ensures that when glanceState is updated, it increases the
+            // Recomposer.changeCount and triggers processEmittableTree.
+            SideEffect { glanceState.value }
         }
     }
 
@@ -207,7 +219,44 @@
         get() = this.getSystemService(Context.APPWIDGET_SERVICE) as AppWidgetManager
 }
 
+internal fun interface ContentReceiver : CoroutineContext.Element {
+    /**
+     * Provide [content] to the Glance session, suspending until the session is
+     * shut down.
+     *
+     * If this function is called concurrently with itself, the previous call will throw
+     * [CancellationException] and the new content will replace it.
+     */
+    suspend fun provideContent(
+        content: @Composable @GlanceComposable () -> Unit
+    ): Nothing
+
+    override val key: CoroutineContext.Key<*> get() = Key
+
+    companion object Key : CoroutineContext.Key<ContentReceiver>
+}
+
+internal fun GlanceAppWidget.runGlance(
+    context: Context,
+    id: GlanceId,
+): Flow<(@GlanceComposable @Composable () -> Unit)?> = channelFlow {
+    val contentCoroutine: AtomicReference<CancellableContinuation<Nothing>?> =
+        AtomicReference(null)
+    val receiver = ContentReceiver { content ->
+        suspendCancellableCoroutine {
+            it.invokeOnCancellation { trySend(null) }
+            contentCoroutine.getAndSet(it)?.cancel()
+            trySend(content)
+        }
+    }
+    withContext(receiver) { provideGlance(context, id) }
+}
+
+internal val Context.appWidgetManager: AppWidgetManager
+    get() = this.getSystemService(Context.APPWIDGET_SERVICE) as AppWidgetManager
+
 internal fun createUniqueRemoteUiName(appWidgetId: Int) = "appWidget-$appWidgetId"
+
 internal fun AppWidgetId.toSessionKey() = createUniqueRemoteUiName(appWidgetId)
 
 /**
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidget.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidget.kt
index 881d186..0dd2fd7 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidget.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidget.kt
@@ -44,8 +44,7 @@
 import androidx.glance.state.GlanceState
 import androidx.glance.state.GlanceStateDefinition
 import androidx.glance.state.PreferencesGlanceStateDefinition
-import java.util.concurrent.atomic.AtomicReference
-import kotlinx.coroutines.CancellableContinuation
+import kotlin.coroutines.coroutineContext
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -53,11 +52,7 @@
 import kotlinx.coroutines.async
 import kotlinx.coroutines.awaitAll
 import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withContext
 
 /**
@@ -74,10 +69,6 @@
     @LayoutRes
     internal val errorUiLayout: Int = R.layout.glance_error_layout,
 ) {
-    internal val contentFlow = MutableStateFlow<(@Composable @GlanceComposable () -> Unit)?>(null)
-    internal val contentCoroutine: AtomicReference<CancellableContinuation<Nothing>?> =
-        AtomicReference(null)
-
     /**
      * Override this function to provide the Glance Composable.
      *
@@ -588,26 +579,7 @@
 suspend fun GlanceAppWidget.provideContent(
     content: @Composable @GlanceComposable () -> Unit
 ): Nothing {
-    suspendCancellableCoroutine<Nothing> {
-        it.invokeOnCancellation {
-            contentFlow.tryEmit(null)
-        }
-        contentCoroutine.getAndSet(it)?.cancel()
-        contentFlow.tryEmit(content)
-    }
-}
-
-/**
- * Returns a cold [kotlinx.coroutines.flow.Flow] that, upon collection, runs
- * [GlanceAppWidget.provideGlance] in a separate coroutine and provides any generated content to
- * the collectors of this flow.
- */
-internal fun GlanceAppWidget.runGlance(
-    context: Context,
-    id: GlanceId
-): Flow<(@Composable @GlanceComposable () -> Unit)?> = flow {
-    coroutineScope {
-        launch { provideGlance(context, id) }
-        contentFlow.collect { emit(it) }
-    }
+    coroutineContext[ContentReceiver]?.provideContent(content)
+        ?: error("provideContent requires a ContentReceiver and should only be called from " +
+            "GlanceAppWidget.provideGlance")
 }
\ No newline at end of file
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/NormalizeCompositionTree.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/NormalizeCompositionTree.kt
index 6f7a7cc..5336388 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/NormalizeCompositionTree.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/NormalizeCompositionTree.kt
@@ -27,6 +27,7 @@
 import androidx.glance.ImageProvider
 import androidx.glance.action.ActionModifier
 import androidx.glance.action.LambdaAction
+import androidx.glance.appwidget.action.CompoundButtonAction
 import androidx.glance.appwidget.lazy.EmittableLazyListItem
 import androidx.glance.background
 import androidx.glance.extractModifier
@@ -136,10 +137,9 @@
     children.foldIndexed(
         mutableMapOf<String, MutableList<LambdaAction>>()
     ) { index, actions, child ->
-        val (actionMod, modifiers) = child.modifier.extractModifier<ActionModifier>()
-        if (actionMod != null && actionMod.action is LambdaAction &&
-            child !is EmittableSizeBox && child !is EmittableLazyListItem) {
-            val action = actionMod.action as LambdaAction
+        val (action: LambdaAction?, modifiers: GlanceModifier) =
+            child.modifier.extractLambdaAction()
+        if (action != null && child !is EmittableSizeBox && child !is EmittableLazyListItem) {
             val newKey = action.key + "+$index"
             val newAction = LambdaAction(newKey, action.block)
             actions.getOrPut(newKey) { mutableListOf() }.add(newAction)
@@ -153,6 +153,17 @@
         actions
     }
 
+private fun GlanceModifier.extractLambdaAction(): Pair<LambdaAction?, GlanceModifier> =
+    extractModifier<ActionModifier>().let { (actionModifier, modifiers) ->
+        val action = actionModifier?.action
+        when {
+            action is LambdaAction -> action to modifiers
+            action is CompoundButtonAction && action.innerAction is LambdaAction ->
+                action.innerAction to modifiers
+            else -> null to modifiers
+        }
+    }
+
 private fun normalizeLazyListItem(view: EmittableLazyListItem) {
     if (view.children.size == 1 && view.alignment == Alignment.CenterStart) return
     val box = EmittableBox()
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/lazy/LazyList.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/lazy/LazyList.kt
index d68d7e7..a9286e32 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/lazy/LazyList.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/lazy/LazyList.kt
@@ -58,7 +58,6 @@
     alignment: Alignment,
     content: LazyListScope.() -> Unit
 ): @Composable () -> Unit {
-    var nextImplicitItemId = ReservedItemIdRangeEnd
     val itemList = mutableListOf<Pair<Long?, @Composable LazyItemScope.() -> Unit>>()
     val listScopeImpl = object : LazyListScope {
         override fun item(itemId: Long, content: @Composable LazyItemScope.() -> Unit) {
@@ -83,8 +82,9 @@
     }
     listScopeImpl.apply(content)
     return {
-        itemList.forEach { (itemId, composable) ->
-            val id = itemId.takeIf { it != LazyListScope.UnspecifiedItemId } ?: nextImplicitItemId--
+        itemList.forEachIndexed { index, (itemId, composable) ->
+            val id = itemId.takeIf { it != LazyListScope.UnspecifiedItemId }
+                ?: (ReservedItemIdRangeEnd - index)
             check(id != LazyListScope.UnspecifiedItemId) { "Implicit list item ids exhausted." }
             LazyListItem(id, alignment) {
                 object : LazyItemScope { }.apply { composable() }
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/lazy/LazyVerticalGrid.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/lazy/LazyVerticalGrid.kt
index cf37b7e..79ace6f 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/lazy/LazyVerticalGrid.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/lazy/LazyVerticalGrid.kt
@@ -61,7 +61,6 @@
     alignment: Alignment,
     content: LazyVerticalGridScope.() -> Unit
 ): @Composable () -> Unit {
-    var nextImplicitItemId = ReservedItemIdRangeEnd
     val itemList = mutableListOf<Pair<Long?, @Composable LazyItemScope.() -> Unit>>()
     val listScopeImpl = object : LazyVerticalGridScope {
         override fun item(itemId: Long, content: @Composable LazyItemScope.() -> Unit) {
@@ -87,9 +86,9 @@
     }
     listScopeImpl.apply(content)
     return {
-        itemList.forEach { (itemId, composable) ->
-            val id = itemId.takeIf {
-              it != LazyVerticalGridScope.UnspecifiedItemId } ?: nextImplicitItemId--
+        itemList.forEachIndexed { index, (itemId, composable) ->
+            val id = itemId.takeIf { it != LazyVerticalGridScope.UnspecifiedItemId }
+                ?: (ReservedItemIdRangeEnd - index)
             check(id != LazyVerticalGridScope.UnspecifiedItemId) {
                 "Implicit list item ids exhausted."
             }
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/AppWidgetSessionTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/AppWidgetSessionTest.kt
index 020f1b8..dd6e6fc 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/AppWidgetSessionTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/AppWidgetSessionTest.kt
@@ -21,6 +21,7 @@
 import android.content.Context
 import android.widget.TextView
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Recomposer
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import androidx.glance.Emittable
@@ -54,7 +55,7 @@
 class AppWidgetSessionTest {
 
     private val id = AppWidgetId(123)
-    private val widget = SampleGlanceAppWidget {}
+    private val widget = TestWidget {}
     private val context = ApplicationProvider.getApplicationContext<Context>()
     private val defaultOptions =
         optionsBundleOf(listOf(DpSize(100.dp, 50.dp), DpSize(50.dp, 100.dp)))
@@ -76,14 +77,17 @@
 
     @Test
     fun provideGlanceRunsGlance() = runTest {
-        session.provideGlance(context).first()
+        runTestingComposition(session.provideGlance(context))
+        assertThat(widget.provideGlanceCalled.get()).isTrue()
     }
 
     @Test
     fun provideGlanceEmitsIgnoreResultForNullContent() = runTest {
         // The session starts out with null content, so we can check that here.
-        val initialContent = session.provideGlance(context).first()
-        val root = runTestingComposition(initialContent)
+        val root = runCompositionUntil(
+            { state, _ -> state == Recomposer.State.Idle },
+            session.provideGlance(context)
+        )
         assertThat(root.shouldIgnoreResult()).isTrue()
     }
 
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/GlanceAppWidgetTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/GlanceAppWidgetTest.kt
index c449dcf..d0984d0 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/GlanceAppWidgetTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/GlanceAppWidgetTest.kt
@@ -40,6 +40,7 @@
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.assertIs
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.collectIndexed
 import kotlinx.coroutines.flow.take
@@ -69,7 +70,7 @@
 
     @Test
     fun createEmptyUi() = fakeCoroutineScope.runTest {
-        val composer = SampleGlanceAppWidget { }
+        val composer = TestWidget { }
 
         val rv = composer.composeForSize(
             context,
@@ -87,7 +88,7 @@
 
     @Test
     fun createUiWithSize() = fakeCoroutineScope.runTest {
-        val composer = SampleGlanceAppWidget {
+        val composer = TestWidget {
             val size = LocalSize.current
             Text("${size.width} x ${size.height}")
         }
@@ -108,7 +109,7 @@
 
     @Test
     fun createUiFromOptionBundle() = fakeCoroutineScope.runTest {
-        val composer = SampleGlanceAppWidget {
+        val composer = TestWidget {
             val options = LocalAppWidgetOptions.current
 
             Text(options.getString("StringKey", "<NOT FOUND>"))
@@ -132,7 +133,7 @@
 
     @Test
     fun createUiFromGlanceId() = fakeCoroutineScope.runTest {
-        val composer = SampleGlanceAppWidget {
+        val composer = TestWidget {
             val glanceId = LocalGlanceId.current
 
             Text(glanceId.toString())
@@ -155,7 +156,7 @@
 
     @Test
     fun createUiWithUniqueMode() = fakeCoroutineScope.runTest {
-        val composer = SampleGlanceAppWidget {
+        val composer = TestWidget {
             val size = LocalSize.current
             Text("${size.width} x ${size.height}")
         }
@@ -188,7 +189,7 @@
     @Config(sdk = [30])
     @Test
     fun createUiWithExactModePreS() = fakeCoroutineScope.runTest {
-        val composer = SampleGlanceAppWidget(SizeMode.Exact) {
+        val composer = TestWidget(SizeMode.Exact) {
             val size = LocalSize.current
             Text("${size.width} x ${size.height}")
         }
@@ -222,7 +223,7 @@
             DpSize(100.dp, 70.dp),
             DpSize(120.dp, 100.dp),
         )
-        val composer = SampleGlanceAppWidget(SizeMode.Responsive(sizes)) {
+        val composer = TestWidget(SizeMode.Responsive(sizes)) {
             val size = LocalSize.current
             Text("${size.width} x ${size.height}")
         }
@@ -253,7 +254,7 @@
     @Test
     fun createUiWithExactMode_noSizeFallsBackToUnique() {
         runBlocking {
-            val composer = SampleGlanceAppWidget(SizeMode.Exact) {
+            val composer = TestWidget(SizeMode.Exact) {
                 val size = LocalSize.current
                 Text("${size.width} x ${size.height}")
             }
@@ -295,7 +296,7 @@
             DpSize(100.dp, 70.dp),
             DpSize(120.dp, 100.dp),
         )
-        val composer = SampleGlanceAppWidget(SizeMode.Responsive(sizes)) {
+        val composer = TestWidget(SizeMode.Responsive(sizes)) {
             val size = LocalSize.current
             Text("${size.width} x ${size.height}")
         }
@@ -458,22 +459,22 @@
     fun cancellingProvideContentEmitsNullContent() = runBlocking {
         val widget = object : GlanceAppWidget() {
             override suspend fun provideGlance(context: Context, id: GlanceId) {
-                val provideContentJob = launch { provideContent { Content() } }
-                delay(100)
-                provideContentJob.cancel()
+                coroutineScope {
+                    val provideContentJob = launch { provideContent { Text("") } }
+                    delay(100)
+                    provideContentJob.cancel()
+                }
             }
             override val sessionManager = GlanceSessionManager
             @Composable
             override fun Content() { }
         }
-        widget.runGlance(context, AppWidgetId(0)).take(3).collectIndexed { index, content ->
+        widget.runGlance(context, AppWidgetId(0)).take(2).collectIndexed { index, content ->
             when (index) {
-                // Initial state of content is null
-                0 -> assertThat(content).isNull()
-                // Content is non-null when provideContent is called
-                1 -> assertThat(content).isNotNull()
+                // Initial content
+                0 -> assertThat(content).isNotNull()
                 // Content is null again when provideContent is cancelled
-                2 -> assertThat(content).isNull()
+                1 -> assertThat(content).isNull()
                 else -> throw Error("Invalid index $index")
             }
         }
@@ -490,16 +491,6 @@
         config.orientation = orientation
         return context.createConfigurationContext(config)
     }
-
-    private class SampleGlanceAppWidget(
-        override val sizeMode: SizeMode = SizeMode.Single,
-        val ui: @Composable () -> Unit,
-    ) : GlanceAppWidget() {
-        @Composable
-        override fun Content() {
-            ui()
-        }
-    }
 }
 
 internal fun optionsBundleOf(sizes: List<DpSize>): Bundle {
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/TestUtils.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/TestUtils.kt
index 8151f2b..4927da4 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/TestUtils.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/TestUtils.kt
@@ -29,40 +29,67 @@
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.widget.FrameLayout
 import android.widget.RemoteViews
-import androidx.compose.runtime.BroadcastFrameClock
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composition
+import androidx.compose.runtime.MonotonicFrameClock
 import androidx.compose.runtime.Recomposer
 import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.TextUnit
 import androidx.core.view.children
 import androidx.glance.Applier
+import androidx.glance.GlanceComposable
+import androidx.glance.GlanceId
+import androidx.glance.session.GlobalSnapshotManager
 import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.currentCoroutineContext
 import kotlinx.coroutines.launch
 import java.util.Locale
+import java.util.concurrent.atomic.AtomicBoolean
 import kotlin.test.assertIs
+import kotlinx.coroutines.flow.first
 
-internal suspend fun runTestingComposition(content: @Composable () -> Unit): RemoteViewsRoot =
+internal suspend fun runTestingComposition(
+    content: @Composable @GlanceComposable () -> Unit,
+): RemoteViewsRoot =
+    runCompositionUntil(
+        stopWhen = { state: Recomposer.State, root: RemoteViewsRoot ->
+            state == Recomposer.State.Idle && !root.shouldIgnoreResult()
+        },
+        content
+    )
+
+internal suspend fun runCompositionUntil(
+    stopWhen: (Recomposer.State, RemoteViewsRoot) -> Boolean,
+    content: @Composable () -> Unit
+): RemoteViewsRoot =
     coroutineScope {
+        GlobalSnapshotManager.ensureStarted()
         val root = RemoteViewsRoot(10)
         val applier = Applier(root)
         val recomposer = Recomposer(currentCoroutineContext())
         val composition = Composition(applier, recomposer)
-        val frameClock = BroadcastFrameClock()
 
         composition.setContent { content() }
 
-        launch(frameClock) { recomposer.runRecomposeAndApplyChanges() }
+        launch(TestFrameClock()) { recomposer.runRecomposeAndApplyChanges() }
 
-        recomposer.close()
+        recomposer.currentState.first { stopWhen(it, root) }
+        recomposer.cancel()
         recomposer.join()
 
         root
     }
 
+/**
+ * Test clock that sends all frames immediately.
+ */
+class TestFrameClock : MonotonicFrameClock {
+    override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R) =
+        onFrame(System.currentTimeMillis())
+}
+
 /** Create the view out of a RemoteViews. */
 internal fun Context.applyRemoteViews(rv: RemoteViews): View {
     val p = Parcel.obtain()
@@ -143,9 +170,21 @@
     return children.mapNotNull { it.findView(predicate, klass) }.firstOrNull()
 }
 
-internal class TestWidget : GlanceAppWidget() {
+internal class TestWidget(
+    override val sizeMode: SizeMode = SizeMode.Single,
+    val ui: @Composable () -> Unit,
+) : GlanceAppWidget() {
+    val provideGlanceCalled = AtomicBoolean(false)
+    override suspend fun provideGlance(
+        context: android.content.Context,
+        id: androidx.glance.GlanceId
+    ) {
+        provideGlanceCalled.set(true)
+        provideContent { Content() }
+    }
     @Composable
     override fun Content() {
+        ui()
     }
 }
 
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/layout/LazyColumnTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/layout/LazyColumnTest.kt
index 32dfd75f..7da7812 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/layout/LazyColumnTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/layout/LazyColumnTest.kt
@@ -140,8 +140,8 @@
         val column = assertIs<EmittableLazyColumn>(root.children.single())
         val listItems = assertAre<EmittableLazyListItem>(column.children)
         assertThat(listItems[0].itemId).isEqualTo(5L)
-        assertThat(listItems[1].itemId).isEqualTo(ReservedItemIdRangeEnd)
-        assertThat(listItems[2].itemId).isEqualTo(ReservedItemIdRangeEnd - 1)
+        assertThat(listItems[1].itemId).isEqualTo(ReservedItemIdRangeEnd - 1)
+        assertThat(listItems[2].itemId).isEqualTo(ReservedItemIdRangeEnd - 2)
         assertThat(listItems[3].itemId).isEqualTo(6L)
     }
 
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/layout/LazyVerticalGridTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/layout/LazyVerticalGridTest.kt
index 06574d4..207f899 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/layout/LazyVerticalGridTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/layout/LazyVerticalGridTest.kt
@@ -149,8 +149,8 @@
         val verticalGrid = assertIs<EmittableLazyVerticalGrid>(root.children.single())
         val listItems = assertAre<EmittableLazyVerticalGridListItem>(verticalGrid.children)
         assertThat(listItems[0].itemId).isEqualTo(5L)
-        assertThat(listItems[1].itemId).isEqualTo(ReservedItemIdRangeEnd)
-        assertThat(listItems[2].itemId).isEqualTo(ReservedItemIdRangeEnd - 1)
+        assertThat(listItems[1].itemId).isEqualTo(ReservedItemIdRangeEnd - 1)
+        assertThat(listItems[2].itemId).isEqualTo(ReservedItemIdRangeEnd - 2)
         assertThat(listItems[3].itemId).isEqualTo(6L)
     }
 
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/session/GlobalSnapshotManager.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/session/GlobalSnapshotManager.kt
index 7a9bba7..578fd4e 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/session/GlobalSnapshotManager.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/session/GlobalSnapshotManager.kt
@@ -16,6 +16,7 @@
 
 package androidx.glance.session
 
+import androidx.annotation.RestrictTo
 import androidx.compose.runtime.snapshots.Snapshot
 import java.util.concurrent.atomic.AtomicBoolean
 import kotlinx.coroutines.CoroutineScope
@@ -31,8 +32,10 @@
  * notifications (which are necessary in order for recompositions to be scheduled in response to
  * state changes). These will be sent on Dispatchers.Default.
  * This is based on [androidx.compose.ui.platform.GlobalSnapshotManager].
+ * @suppress
  */
-internal object GlobalSnapshotManager {
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+object GlobalSnapshotManager {
     private val started = AtomicBoolean(false)
 
     fun ensureStarted() {
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/session/Session.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/session/Session.kt
index 52010a0..cedec4c 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/session/Session.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/session/Session.kt
@@ -23,7 +23,6 @@
 import androidx.glance.GlanceComposable
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.channels.ClosedReceiveChannelException
-import kotlinx.coroutines.flow.Flow
 
 typealias SetContentFn = suspend (@Composable @GlanceComposable () -> Unit) -> Unit
 
@@ -45,9 +44,7 @@
     /**
      * Provide the Glance composable to be run in the [androidx.compose.runtime.Composition].
      */
-    abstract suspend fun provideGlance(
-        context: Context,
-    ): Flow<@Composable @GlanceComposable () -> Unit>
+    abstract fun provideGlance(context: Context): @Composable @GlanceComposable () -> Unit
 
     /**
      * Process the Emittable tree that results from the running the composable provided by
@@ -55,18 +52,12 @@
      *
      * This will also be called for the results of future recompositions.
      */
-    abstract suspend fun processEmittableTree(
-        context: Context,
-        root: EmittableWithChildren,
-    )
+    abstract suspend fun processEmittableTree(context: Context, root: EmittableWithChildren)
 
     /**
      * Process an event that was sent to this session.
      */
-    abstract suspend fun processEvent(
-        context: Context,
-        event: Any,
-    )
+    abstract suspend fun processEvent(context: Context, event: Any)
 
     /**
      * Enqueues an [event] to be processed by the session.
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/session/SessionWorker.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/session/SessionWorker.kt
index 5ac0d49..586e6fa 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/session/SessionWorker.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/session/SessionWorker.kt
@@ -20,9 +20,7 @@
 import android.util.Log
 import androidx.annotation.VisibleForTesting
 import androidx.compose.runtime.Composition
-import androidx.compose.runtime.RecomposeScope
 import androidx.compose.runtime.Recomposer
-import androidx.compose.runtime.currentRecomposeScope
 import androidx.glance.Applier
 import androidx.glance.EmittableWithChildren
 import androidx.work.CoroutineWorker
@@ -65,37 +63,32 @@
         GlobalSnapshotManager.ensureStarted()
         val root = session.createRootEmittable()
         val recomposer = Recomposer(coroutineContext)
-        val composition = Composition(Applier(root), recomposer)
-        val contentReady = MutableStateFlow(false)
-        val uiReady = MutableStateFlow(false)
-        val provideGlance = launch {
-            var recomposeScope: RecomposeScope? = null
-            session.provideGlance(applicationContext).collect { content ->
-                composition.setContent {
-                    recomposeScope = currentRecomposeScope
-                    content()
-                }
-                // Trigger recomposition. This is necessary when calling setContent multiple times.
-                recomposeScope?.invalidate()
-                contentReady.emit(true)
-            }
+        val composition = Composition(Applier(root), recomposer).apply {
+            setContent(session.provideGlance(applicationContext))
         }
+        val uiReady = MutableStateFlow(false)
 
-        contentReady.first { it }
         launch(frameClock) {
             recomposer.runRecomposeAndApplyChanges()
         }
         launch {
+            var lastRecomposeCount = recomposer.changeCount
             recomposer.currentState.collect { state ->
                 if (DEBUG) Log.d(TAG, "Recomposer(${session.key}): currentState=$state")
                 when (state) {
                     Recomposer.State.Idle -> {
-                        if (DEBUG) Log.d(TAG, "UI tree ready (${session.key})")
-                        session.processEmittableTree(
-                            applicationContext,
-                            root.copy() as EmittableWithChildren
-                        )
-                        uiReady.emit(true)
+                        // Only update the session when a change has actually occurred. The
+                        // Recomposer may sometimes wake up due to changes in other compositions.
+                        // Also update the session if we have not sent an initial tree yet.
+                        if (recomposer.changeCount > lastRecomposeCount || !uiReady.value) {
+                            if (DEBUG) Log.d(TAG, "UI tree updated (${session.key})")
+                            session.processEmittableTree(
+                                applicationContext,
+                                root.copy() as EmittableWithChildren
+                            )
+                            if (!uiReady.value) uiReady.emit(true)
+                        }
+                        lastRecomposeCount = recomposer.changeCount
                     }
                     Recomposer.State.ShutDown -> cancel()
                     else -> {}
@@ -103,6 +96,7 @@
             }
         }
 
+        // Wait until the Emittable tree has been processed at least once before receiving events.
         uiReady.first { it }
         session.receiveEvents(applicationContext) {
             if (DEBUG) Log.d(TAG, "processing event for ${session.key}")
@@ -110,7 +104,6 @@
         }
 
         composition.dispose()
-        provideGlance.cancel()
         frameClock.stopInteractive()
         recomposer.close()
         recomposer.join()
diff --git a/glance/glance/src/test/kotlin/androidx/glance/session/SessionManagerImplTest.kt b/glance/glance/src/test/kotlin/androidx/glance/session/SessionManagerImplTest.kt
index 303d9ca..9ceb156 100644
--- a/glance/glance/src/test/kotlin/androidx/glance/session/SessionManagerImplTest.kt
+++ b/glance/glance/src/test/kotlin/androidx/glance/session/SessionManagerImplTest.kt
@@ -31,7 +31,6 @@
 import com.google.common.truth.Truth.assertThat
 import kotlin.coroutines.suspendCoroutine
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.test.runTest
 import org.junit.After
 import org.junit.Before
@@ -47,9 +46,9 @@
             TODO("Not yet implemented")
         }
 
-        override suspend fun provideGlance(
+        override fun provideGlance(
             context: Context
-        ): Flow<@Composable @GlanceComposable () -> Unit> {
+        ): @Composable @GlanceComposable () -> Unit {
             TODO("Not yet implemented")
         }
 
diff --git a/glance/glance/src/test/kotlin/androidx/glance/session/SessionWorkerTest.kt b/glance/glance/src/test/kotlin/androidx/glance/session/SessionWorkerTest.kt
index f78bf74..24a428a 100644
--- a/glance/glance/src/test/kotlin/androidx/glance/session/SessionWorkerTest.kt
+++ b/glance/glance/src/test/kotlin/androidx/glance/session/SessionWorkerTest.kt
@@ -33,17 +33,17 @@
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.assertIs
 import kotlin.test.assertNotNull
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(RobolectricTestRunner::class)
 class SessionWorkerTest {
     private val sessionManager = TestSessionManager()
@@ -62,7 +62,7 @@
     }
 
     @Test
-    fun createSessionWorker() = runBlocking {
+    fun createSessionWorker() = runTest {
         launch {
             val result = worker.doWork()
             assertThat(result).isEqualTo(Result.success())
@@ -72,7 +72,7 @@
     }
 
     @Test
-    fun sessionWorkerRunsComposition() = runBlocking {
+    fun sessionWorkerRunsComposition() = runTest {
         launch {
             val result = worker.doWork()
             assertThat(result).isEqualTo(Result.success())
@@ -90,7 +90,7 @@
     }
 
     @Test
-    fun sessionWorkerCallsProvideGlance(): Unit = runBlocking {
+    fun sessionWorkerCallsProvideGlance(): Unit = runTest {
         launch {
             val result = worker.doWork()
             assertThat(result).isEqualTo(Result.success())
@@ -102,7 +102,7 @@
     }
 
     @Test
-    fun sessionWorkerStateChangeTriggersRecomposition() = runBlocking {
+    fun sessionWorkerStateChangeTriggersRecomposition() = runTest {
         launch {
             val result = worker.doWork()
             assertThat(result).isEqualTo(Result.success())
@@ -126,7 +126,7 @@
     }
 
     @Test
-    fun sessionWorkerReceivesActions() = runBlocking {
+    fun sessionWorkerReceivesActions() = runTest {
         launch {
             val result = worker.doWork()
             assertThat(result).isEqualTo(Result.success())
@@ -195,11 +195,11 @@
     }
 
     var provideGlanceCalled = 0
-    override suspend fun provideGlance(
+    override fun provideGlance(
         context: Context
-    ): Flow<@Composable @GlanceComposable () -> Unit> {
+    ): @Composable @GlanceComposable () -> Unit {
         provideGlanceCalled++
-        return flowOf(content)
+        return content
     }
 
     override suspend fun processEmittableTree(context: Context, root: EmittableWithChildren) {
diff --git a/gradle.properties b/gradle.properties
index 975ac71..5868acd 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -57,9 +57,14 @@
 kotlin.mpp.stability.nowarn=true
 # b/227307216
 kotlin.mpp.absentAndroidTarget.nowarn=true
+# b/261241595
+kotlin.mpp.androidSourceSetLayoutVersion=1
+kotlin.mpp.androidSourceSetLayoutVersion1.nowarn=true
 # As of October 3 2022, AGP 7.4.0-alpha08 is higher than AGP 7.3
 # Presumably complains if using a non-stable AGP, which we are regularly doing to test pre-stable.
 kotlin.mpp.androidGradlePluginCompatibility.nowarn=true
+# Until we get a newer AGP which doesn't do this
+kotlin.options.suppressFreeCompilerArgsModificationWarning=true
 
 # Properties we often want to toggle
 # ksp.version.check=false
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 14270a5..7a5b69b 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -35,13 +35,13 @@
 hilt = "2.44"
 incap = "0.2"
 jcodec = "0.2.5"
-kotlin = "1.7.21"
+kotlin = "1.8.0"
 kotlinBenchmark = "0.4.7"
-kotlinNative = "1.7.21"
+kotlinNative = "1.8.0"
 kotlinCompileTesting = "1.4.9"
 kotlinCoroutines = "1.6.4"
 kotlinSerialization = "1.3.3"
-ksp = "1.7.21-1.0.8"
+ksp = "1.8.0-1.0.8"
 ktlint = "0.46.0-20220520.192227-74"
 leakcanary = "2.8.1"
 media3 = "1.0.0-beta03"
@@ -146,6 +146,7 @@
 gradleIncapHelperProcessor = { module = "net.ltgt.gradle.incap:incap-processor", version.ref = "incap" }
 kotlinAnnotationProcessingEmbeddable = { module = "org.jetbrains.kotlin:kotlin-annotation-processing-embeddable", version.ref = "kotlin" }
 kotlinBenchmarkRuntime = { module = "org.jetbrains.kotlinx:kotlinx-benchmark-runtime", version.ref = "kotlinBenchmark" }
+kotlinBom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" }
 kotlinCompiler = { module = "org.jetbrains.kotlin:kotlin-compiler", version.ref = "kotlin" }
 kotlinCompilerEmbeddable = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlin" }
 kotlinCompileTesting = { module = "com.github.tschuchortdev:kotlin-compile-testing", version.ref = "kotlinCompileTesting" }
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index c569cdd..1cf4e56 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -453,6 +453,7 @@
          <trusted-key id="e0cb7823cfd00fbf" group="com.jakewharton.android.repackaged"/>
          <trusted-key id="e0d98c5fd55a8af232290e58dee12b9896f97e34" group="org.pcollections"/>
          <trusted-key id="e16ab52d79fd224f" group="com.google.api.grpc"/>
+         <trusted-key id="e3a9f95079e84ce201f7cf60bede11eaf1164480" group="org.hamcrest"/>
          <trusted-key id="e62231331bca7e1f292c9b88c1b12a5d99c0729d" group="org.jetbrains"/>
          <trusted-key id="e77417ac194160a3fabd04969a259c7ee636c5ed">
             <trusting group="com.google.errorprone"/>
@@ -910,21 +911,21 @@
             <sha256 value="4e54622f5dc0f8b6c51e28650268f001e3b55d076c8e3a9d9731c050820c0a3d" origin="Generated by Gradle"/>
          </artifact>
       </component>
-      <component group="" name="kotlin-native-prebuilt-linux-x86_64" version="1.7.21" androidx:reason="Unsigned, b/227204920">
-         <artifact name="kotlin-native-prebuilt-linux-x86_64-1.7.21.tar.gz">
-            <sha256 value="b6a4aef343c029ac1b7d3e70101c45356a02a30b10fdd0813fb085b29cc714f4" origin="Hand-built using sha256sum kotlin-native-prebuilt-linux-x86_64-1.7.21.tar.gz"/>
+      <component group="" name="kotlin-native-prebuilt-linux-x86_64" version="1.8.0" androidx:reason="Unsigned, b/227204920">
+         <artifact name="kotlin-native-prebuilt-linux-x86_64-1.8.0.tar.gz">
+            <sha256 value="841582a0259eb0440e90f8ac6c71bd164ac1dcd01eef02c66ac9852179a86d9f" origin="Hand-built using sha256sum kotlin-native-prebuilt-linux-x86_64-1.7.21.tar.gz"/>
          </artifact>
       </component>
 
-      <component group="" name="kotlin-native-prebuilt-macos-aarch64" version="1.7.21">
-         <artifact name="kotlin-native-prebuilt-macos-aarch64-1.7.21.tar.gz">
-            <sha256 value="6953ddaa5ed2466ac606bd81338475af8064dce6a932aa51baaa0d3bff64bcc2" origin="Hand-built using sha256sum kotlin-native-prebuilt-macos-aarch64-1.7.21.tar.gz"/>
+      <component group="" name="kotlin-native-prebuilt-macos-aarch64" version="1.8.0">
+         <artifact name="kotlin-native-prebuilt-macos-aarch64-1.8.0.tar.gz">
+            <sha256 value="83022c4b47d5e0c261dd4844ec775c3cedc998d08e8cff07a9318b309ca7fbf1" origin="Hand-built using sha256sum kotlin-native-prebuilt-macos-aarch64-1.7.21.tar.gz"/>
          </artifact>
       </component>
 
-      <component group="" name="kotlin-native-prebuilt-macos-x86_64" version="1.7.21">
-         <artifact name="kotlin-native-prebuilt-macos-x86_64-1.7.21.tar.gz">
-            <sha256 value="ee01ebb7f44f8acc0aad87b61219f292dd766a06acb6ecba9fb21c5834c747eb" origin="Hand-built using sha256sum kotlin-native-prebuilt-macos-x86_64-1.7.21.tar.gz"/>
+      <component group="" name="kotlin-native-prebuilt-macos-x86_64" version="1.8.0">
+         <artifact name="kotlin-native-prebuilt-macos-x86_64-1.8.0.tar.gz">
+            <sha256 value="27f9b7de732ce36b3daf291f7970762ba7e538cf1eb75603dd2377ae8ebf9513" origin="Hand-built using sha256sum kotlin-native-prebuilt-macos-x86_64-1.7.21.tar.gz"/>
          </artifact>
       </component>
    </components>
diff --git a/graphics/filters/OWNERS b/graphics/filters/OWNERS
index 1d01c7c..6c8a8b5 100644
--- a/graphics/filters/OWNERS
+++ b/graphics/filters/OWNERS
@@ -3,4 +3,5 @@
 chet@google.com
 xxayedawgxx@google.com
 andrewlewis@google.com
-huangdarwin@google.com
\ No newline at end of file
+huangdarwin@google.com
+chpitts@google.com
diff --git a/graphics/filters/filters/build.gradle b/graphics/filters/filters/build.gradle
index d61d155..e2d3e79 100644
--- a/graphics/filters/filters/build.gradle
+++ b/graphics/filters/filters/build.gradle
@@ -23,16 +23,40 @@
 }
 
 dependencies {
+    def media3Version = '1.0.0-beta03'
+
     api(libs.kotlinStdlib)
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testCore)
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.testRules)
     androidTestImplementation("androidx.test:core:1.4.0@aar")
+
+    // Add dependencies here
+    implementation('androidx.media3:media3-effect:' + media3Version)
+    implementation('androidx.media3:media3-common:' + media3Version)
+    implementation('androidx.media3:media3-ui:' + media3Version)
+    implementation('androidx.media3:media3-exoplayer:' + media3Version)
+    implementation('androidx.media3:media3-transformer:' + media3Version)
+
 }
 
 android {
+    defaultConfig {
+        minSdkVersion 21
+    }
+
     namespace "androidx.graphics.filters"
+    buildFeatures {
+        viewBinding true
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
 }
 
 androidx {
diff --git a/graphics/filters/filters/src/androidTest/AndroidManifest.xml b/graphics/filters/filters/src/androidTest/AndroidManifest.xml
index 8855711..f31f8d9 100644
--- a/graphics/filters/filters/src/androidTest/AndroidManifest.xml
+++ b/graphics/filters/filters/src/androidTest/AndroidManifest.xml
@@ -16,15 +16,18 @@
   -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
 
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+
     <application>
         <activity android:name="androidx.graphics.filters.TestFiltersActivity"
-            android:label="Graphics Filters"
+            android:label="Video Filter"
             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
-
     </application>
 </manifest>
diff --git a/graphics/filters/filters/src/androidTest/kotlin/androidx/graphics/filters/TestFiltersActivity.kt b/graphics/filters/filters/src/androidTest/kotlin/androidx/graphics/filters/TestFiltersActivity.kt
index 18c0b07..fbc57cf 100644
--- a/graphics/filters/filters/src/androidTest/kotlin/androidx/graphics/filters/TestFiltersActivity.kt
+++ b/graphics/filters/filters/src/androidTest/kotlin/androidx/graphics/filters/TestFiltersActivity.kt
@@ -16,31 +16,288 @@
 
 package androidx.graphics.filters
 
+import android.Manifest.permission.READ_EXTERNAL_STORAGE
 import android.app.Activity
+import android.content.pm.PackageManager
+import android.net.Uri
 import android.os.Bundle
+import android.os.Handler
 import android.view.Gravity
+import android.view.View
 import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.widget.Button
 import android.widget.FrameLayout
+import android.widget.LinearLayout
+import android.widget.ScrollView
 import android.widget.TextView
+import androidx.media3.common.Effect
+import androidx.media3.common.MediaItem
+import androidx.media3.common.MediaLibraryInfo.TAG
+import androidx.media3.common.util.Log
+import androidx.media3.common.util.Util
+import androidx.media3.exoplayer.ExoPlayer
+import androidx.media3.transformer.DefaultEncoderFactory
+import androidx.media3.transformer.ProgressHolder
+import androidx.media3.transformer.TransformationException
+import androidx.media3.transformer.TransformationRequest
+import androidx.media3.transformer.TransformationResult
+import androidx.media3.transformer.Transformer
+import androidx.media3.ui.PlayerView
+import com.google.common.collect.ImmutableList
+import java.io.File
+import java.io.IOException
+
+private val PRESET_FILE_URIS =
+  arrayOf(
+    "https://storage.googleapis.com/exoplayer-test-media-1/mp4/android-screens-10s.mp4",
+    "https://storage.googleapis.com/exoplayer-test-media-0/android-block-1080-hevc.mp4",
+    "https://html5demos.com/assets/dizzy.mp4",
+    "https://html5demos.com/assets/dizzy.webm",
+    "https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_4k60.mp4",
+    "https://storage.googleapis.com/exoplayer-test-media-1/mp4/8k24fps_4s.mp4",
+    "https://storage.googleapis.com/exoplayer-test-media-1/mp4/1920w_1080h_4s.mp4",
+    "https://storage.googleapis.com/exoplayer-test-media-0/BigBuckBunny_320x180.mp4",
+    "https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_avc_aac.mp4",
+    "https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_rotated_avc_aac.mp4",
+    "https://storage.googleapis.com/exoplayer-test-media-internal-63834241aced7884c2544af1a" +
+      "3452e01/mp4/slow%20motion/slowMotion_countdown_120fps.mp4",
+    "https://storage.googleapis.com/exoplayer-test-media-1/mp4/slow-motion/" +
+      "slowMotion_stopwatch_240fps_long.mp4",
+    "https://storage.googleapis.com/exoplayer-test-media-1/gen/screens/dash-vod-single-segment/" +
+      "manifest-baseline.mpd",
+    "https://storage.googleapis.com/exoplayer-test-media-1/mp4/samsung-s21-hdr-hdr10.mp4",
+    "https://storage.googleapis.com/exoplayer-test-media-1/mp4/Pixel7Pro_HLG_1080P.mp4",
+    "https://storage.googleapis.com/exoplayer-test-media-internal-63834241aced7884c2544af1a3452" +
+      "e01/mp4/sony-hdr-hlg-full-range.mp4"
+  )
 
 class TestFiltersActivity : Activity() {
 
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        setContentView(
-            FrameLayout(this).apply {
-                layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+  private var outputFile: File? = null
+  private var transformer: Transformer? = null
+  private var sourcePlayer: ExoPlayer? = null
+  private var filteredPlayer: ExoPlayer? = null
+  private var sourcePlayerView: PlayerView? = null
+  private var filteredPlayerView: PlayerView? = null
+  private var filterButton: Button? = null
+  private var statusBar: TextView? = null
+
+  override fun onCreate(savedInstanceState: Bundle?) {
+    super.onCreate(savedInstanceState)
+
+    sourcePlayerView = PlayerView(this@TestFiltersActivity)
+    sourcePlayerView!!.minimumHeight = 480
+    sourcePlayerView!!.minimumWidth = 640
+    sourcePlayerView!!.layoutParams = ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
+    filteredPlayerView = PlayerView(this@TestFiltersActivity)
+    filteredPlayerView!!.minimumHeight = 480
+    filteredPlayerView!!.minimumWidth = 640
+    filteredPlayerView!!.layoutParams = ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
+
+    statusBar = TextView(this)
+
+    setContentView(
+      FrameLayout(this).apply {
+        layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+
+        addView(
+          ScrollView(this@TestFiltersActivity).apply {
+            layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
+            addView(
+              LinearLayout(this@TestFiltersActivity).apply {
+                layoutParams = ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
+                orientation = LinearLayout.VERTICAL
                 addView(
-                    TextView(this@TestFiltersActivity).apply {
-                        layoutParams = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
-                            gravity = Gravity.CENTER
-                        }
-                        text = "Hello filters!"
-                    }
+                  TextView(this@TestFiltersActivity).apply {
+                    layoutParams =
+                      LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
+                        gravity = Gravity.LEFT
+                      }
+                    text = "Source Video"
+                  }
                 )
-            }
+                addView(sourcePlayerView)
+                addView(
+                  TextView(this@TestFiltersActivity).apply {
+                    layoutParams =
+                      LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply {
+                        gravity = Gravity.LEFT
+                      }
+                    text = "Filtered Video"
+                  }
+                )
+                addView(filteredPlayerView)
+                addView(statusBar)
+                addView(createControls())
+              }
+            )
+          }
         )
+      }
+    )
+  }
+
+  private fun createControls(): View {
+    this.filterButton = Button(this)
+    this.filterButton!!.text = "Run Filter"
+    this.filterButton!!.setOnClickListener(
+      View.OnClickListener { this@TestFiltersActivity.startTransformation() }
+    )
+
+    val controls =
+      LinearLayout(this).apply {
+        layoutParams = ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
+        orientation = LinearLayout.HORIZONTAL
+
+        addView(this@TestFiltersActivity.filterButton)
+      }
+
+    return controls
+  }
+
+  private fun startTransformation() {
+    statusBar!!.text = "Request permissions"
+    requestPermission()
+    statusBar!!.text = "Setup transformation"
+
+    val mediaUri = Uri.parse(PRESET_FILE_URIS[0])
+    try {
+      outputFile = createExternalCacheFile("filters-output.mp4")
+      val outputFilePath: String = outputFile!!.getAbsolutePath()
+      val mediaItem: MediaItem = createMediaItem(mediaUri)
+      var transformer: Transformer = createTransformer(outputFilePath)
+      transformer.startTransformation(mediaItem, outputFilePath)
+      this.transformer = transformer
+    } catch (e: IOException) {
+      throw IllegalStateException(e)
     }
-}
\ No newline at end of file
+    val mainHandler = Handler(mainLooper)
+    val progressHolder = ProgressHolder()
+    mainHandler.post(
+      object : Runnable {
+        override fun run() {
+          if (
+            transformer?.getProgress(progressHolder) !=
+              Transformer.PROGRESS_STATE_NO_TRANSFORMATION
+          ) {
+            mainHandler.postDelayed(/* r= */ this, /* delayMillis= */ 500)
+          }
+        }
+      }
+    )
+  }
+
+  private fun createTransformer(filePath: String): Transformer {
+    val transformerBuilder = Transformer.Builder(/* context= */ this)
+    val effects: List<Effect> = createEffectsList()
+
+    val requestBuilder = TransformationRequest.Builder()
+    transformerBuilder
+      .setTransformationRequest(requestBuilder.build())
+      .setEncoderFactory(
+        DefaultEncoderFactory.Builder(this.applicationContext).setEnableFallback(false).build()
+      )
+    transformerBuilder.setVideoEffects(effects)
+
+    return transformerBuilder
+      .addListener(
+        object : Transformer.Listener {
+          override fun onTransformationCompleted(
+            mediaItem: MediaItem,
+            transformationResult: TransformationResult
+          ) {
+            this@TestFiltersActivity.onTransformationCompleted(filePath, mediaItem)
+          }
+
+          override fun onTransformationError(
+            mediaItem: MediaItem,
+            exception: TransformationException
+          ) {
+            this@TestFiltersActivity.onTransformationError(exception)
+          }
+        }
+      )
+      .build()
+  }
+
+  private fun onTransformationError(exception: TransformationException) {
+    statusBar!!.text = "Transformation error: " + exception.message
+    Log.e(TAG, "Transformation error", exception)
+  }
+
+  private fun onTransformationCompleted(filePath: String, inputMediaItem: MediaItem?) {
+    statusBar!!.text = "Transformation success!"
+    Log.d(TAG, "Output file path: file://$filePath")
+    playMediaItems(inputMediaItem, MediaItem.fromUri("file://" + filePath))
+  }
+
+  private fun playMediaItems(inputMediaItem: MediaItem?, outputMediaItem: MediaItem) {
+    sourcePlayerView!!.player = null
+    filteredPlayerView!!.player = null
+
+    releasePlayer()
+    var sourcePlayer = ExoPlayer.Builder(/* context= */ this).build()
+    sourcePlayerView!!.player = sourcePlayer
+    sourcePlayerView!!.controllerAutoShow = false
+    if (inputMediaItem != null) {
+      sourcePlayer.setMediaItem(inputMediaItem)
+    }
+    sourcePlayer.prepare()
+    this.sourcePlayer = sourcePlayer
+    sourcePlayer.volume = 0f
+    var filteredPlayer = ExoPlayer.Builder(/* context= */ this).build()
+    filteredPlayerView!!.player = filteredPlayer
+    filteredPlayerView!!.controllerAutoShow = false
+    filteredPlayer.setMediaItem(outputMediaItem)
+    filteredPlayer.prepare()
+    this.filteredPlayer = filteredPlayer
+    sourcePlayer.play()
+    filteredPlayer.play()
+  }
+
+  private fun releasePlayer() {
+    if (sourcePlayer != null) {
+      sourcePlayer!!.release()
+      sourcePlayer = null
+    }
+    if (filteredPlayer != null) {
+      filteredPlayer!!.release()
+      filteredPlayer = null
+    }
+  }
+
+  private fun createEffectsList(): List<Effect> {
+    val effects = ImmutableList.Builder<Effect>()
+
+    effects.add(Vignette(0.5f, 0.75f))
+
+    return effects.build()
+  }
+
+  private fun requestPermission() {
+    if (Util.SDK_INT < 23) {
+      return
+    }
+
+    if (checkSelfPermission(READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+      requestPermissions(Array<String>(1) { READ_EXTERNAL_STORAGE }, /* requestCode= */ 0)
+    }
+  }
+
+  @Throws(IOException::class)
+  private fun createExternalCacheFile(fileName: String): File? {
+    val file = File(externalCacheDir, fileName)
+    check(!(file.exists() && !file.delete())) {
+      "Could not delete the previous transformer output file"
+    }
+    check(file.createNewFile()) { "Could not create the transformer output file" }
+    return file
+  }
+
+  private fun createMediaItem(uri: Uri): MediaItem {
+    val mediaItemBuilder = MediaItem.Builder().setUri(uri)
+    return mediaItemBuilder.build()
+  }
+}
diff --git a/graphics/filters/filters/src/main/assets/shaders/fragment_shader_vignette_es2.glsl b/graphics/filters/filters/src/main/assets/shaders/fragment_shader_vignette_es2.glsl
new file mode 100644
index 0000000..94012b8
--- /dev/null
+++ b/graphics/filters/filters/src/main/assets/shaders/fragment_shader_vignette_es2.glsl
@@ -0,0 +1,67 @@
+#version 100
+// 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.
+
+// ES2 Vignetting fragment shader
+precision highp float;
+uniform sampler2D uTexSampler;
+
+uniform int uShouldVignetteColor;
+uniform int uShouldVignetteAlpha;
+uniform float uInnerRadius;
+uniform float uOuterRadius;
+uniform float uAspectRatio;
+varying vec2 vTexSamplingCoord;
+
+void main() {
+    vec4 inputColor = texture2D(uTexSampler, vTexSamplingCoord);
+
+    // Update x and y coords to [-1, 1]
+    float recenteredX = 2.0 * (vTexSamplingCoord.x - 0.5);
+    float recenteredY = 2.0 * (vTexSamplingCoord.y - 0.5);
+
+    // Un-normalize y coord so that the size of units match on x and y axes, based on an x-axis
+    // with data in [-1, 1]
+    float aspectCorrectY = recenteredY / uAspectRatio;
+    float invAspectRatio = 1.0 / uAspectRatio;
+    // Calculate maxRadius, the distance from the center to a corner of the image.
+    float maxRadius = sqrt(1.0 + invAspectRatio * invAspectRatio);
+    // Calculate radial position of the current texture coordinate.
+    float radius = sqrt(recenteredX * recenteredX + aspectCorrectY * aspectCorrectY);
+    // Normalize the radius based on the distance to the corner.
+    float normalizedRadius = radius / maxRadius;
+
+    // Calculate the vignette amount.  Data outside of the outer radius is set to 0.  Data inside of
+    // the inner radius is unchanged.  Data between is interpolated linearly.
+    float vignetteAmount = 0.0;
+    if (normalizedRadius > uOuterRadius) {
+        vignetteAmount = 1.0;
+    } else if (normalizedRadius > uInnerRadius) {
+        vignetteAmount = (normalizedRadius - uInnerRadius) / (uOuterRadius - uInnerRadius);
+    }
+
+    // Apply the vignetting.
+    gl_FragColor.rgba = inputColor.rgba;
+
+    bool vignetteAlpha = uShouldVignetteAlpha > 0;
+    bool vignetteColor = uShouldVignetteColor > 0;
+
+    if (vignetteColor && vignetteAlpha) {
+        gl_FragColor.rgba = inputColor.rgba * (1.0 - vignetteAmount);
+    } else if (vignetteColor) {
+        gl_FragColor.rgb = inputColor.rgb * (1.0 - vignetteAmount);
+    } else if (vignetteAlpha) {
+        gl_FragColor.a = inputColor.a * (1.0 - vignetteAmount);
+    }
+}
\ No newline at end of file
diff --git a/graphics/filters/filters/src/main/assets/shaders/vertex_shader_transformation_es2.glsl b/graphics/filters/filters/src/main/assets/shaders/vertex_shader_transformation_es2.glsl
new file mode 100644
index 0000000..791e52e
--- /dev/null
+++ b/graphics/filters/filters/src/main/assets/shaders/vertex_shader_transformation_es2.glsl
@@ -0,0 +1,27 @@
+#version 100
+// Copyright 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// ES 2 vertex shader that applies the 4 * 4 transformation matrices
+// uTransformationMatrix and the uTexTransformationMatrix.
+
+attribute vec4 aFramePosition;
+uniform mat4 uTransformationMatrix;
+uniform mat4 uTexTransformationMatrix;
+varying vec2 vTexSamplingCoord;
+void main() {
+    gl_Position = uTransformationMatrix * aFramePosition;
+    vec4 texturePosition = vec4(aFramePosition.x * 0.5 + 0.5, aFramePosition.y * 0.5 + 0.5, 0.0, 1.0);
+    vTexSamplingCoord = (uTexTransformationMatrix * texturePosition).xy;
+}
\ No newline at end of file
diff --git a/graphics/filters/filters/src/main/java/androidx/graphics/filters/Vignette.kt b/graphics/filters/filters/src/main/java/androidx/graphics/filters/Vignette.kt
new file mode 100644
index 0000000..9bf46f3
--- /dev/null
+++ b/graphics/filters/filters/src/main/java/androidx/graphics/filters/Vignette.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.graphics.filters
+
+import android.content.Context
+import androidx.annotation.FloatRange
+import androidx.media3.common.FrameProcessingException
+import androidx.media3.common.util.Assertions
+import androidx.media3.effect.GlEffect
+import androidx.media3.effect.SingleFrameGlTextureProcessor
+
+/** A {@link androidx.media3.common.GlEffect} to apply a vignetting effect to image data.
+ * The vignetting may be applied to color and/or alpha channels.
+ *
+ * Inner radius and outer radius are in [0,1], normalized by the distance from the center of the
+ * image to a corner.  A pixels within the inner radius are unaffected, while affected pixel values
+ * outside the outer radius are set to 0.  Pixels between the inner and outer radius are
+ * interpolated linearly.
+ *
+ * @param innerRadius Radius in [0,1], normalized by the distance from image center to corner. All
+ *        pixels within innerRadius are unaffected by the vignette.
+ * @param outerRadius Radius in [0,1], normalized by the distance from image center to corner. All
+ *        pixels outside of outerRadius are fully vignetted.
+ * @param vignetteStyle Enum indicating to which channels vignetting should be applied.
+ */
+internal class Vignette(
+  @FloatRange(from = 0.0, to = 1.0) innerRadius: Float,
+  @FloatRange(from = 0.0, to = 1.0) outerRadius: Float,
+  vignetteStyle: VignetteStyle = VignetteStyle.COLOR,
+) : GlEffect {
+
+  enum class VignetteStyle {
+    COLOR, // Vignetting is applied to color channels only.
+    ALPHA, // Vignetting is applied to alpha channel only.
+    COLOR_AND_ALPHA // Vignetting is applied to color and alpha channels.
+  }
+
+  private val mInnerRadius: Float
+  val innerRadius get() = this.mInnerRadius
+
+  private val mOuterRadius: Float
+  val outerRadius get() = this.mOuterRadius
+
+  private val mVignetteStyle: VignetteStyle
+  val vignetteStyle get() = this.mVignetteStyle
+
+  init {
+    Assertions.checkArgument(
+      innerRadius in 0.0..1.0,
+      "InnerRadius needs to be in the interval [0, 1]."
+    )
+    Assertions.checkArgument(
+      outerRadius in innerRadius..1.0F,
+      "InnerRadius needs to be in the interval [innerRadius, 1]."
+    )
+
+    this.mInnerRadius = innerRadius
+    this.mOuterRadius = outerRadius
+    this.mVignetteStyle = vignetteStyle
+  }
+
+  // media3 GlEffect does not annotate nullability of toGlTextureProcessor.  Cannot override
+  // toGlTextureProcessor and satisfy API lint simultaneously.
+  @Throws(FrameProcessingException::class)
+  override fun toGlTextureProcessor(
+    @Suppress("InvalidNullabilityOverride") // Remove when b/264908709 is resolved.
+    context: Context,
+    useHdr: Boolean
+  ): SingleFrameGlTextureProcessor {
+    return VignetteProcessor(context, this, useHdr)
+  }
+}
diff --git a/graphics/filters/filters/src/main/java/androidx/graphics/filters/VignetteProcessor.kt b/graphics/filters/filters/src/main/java/androidx/graphics/filters/VignetteProcessor.kt
new file mode 100644
index 0000000..2ab3d6d
--- /dev/null
+++ b/graphics/filters/filters/src/main/java/androidx/graphics/filters/VignetteProcessor.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.graphics.filters
+
+import android.content.Context
+import android.opengl.GLES20
+import android.util.Pair
+import androidx.media3.common.FrameProcessingException
+import androidx.media3.common.util.GlProgram
+import androidx.media3.common.util.GlUtil
+import androidx.media3.effect.SingleFrameGlTextureProcessor
+import java.io.IOException
+
+/** Applies a {@link Vignette} effect to each frame in the fragment shader. */
+
+/**
+ * Creates a new instance.
+ *
+ * @param context The {@link Context}.
+ * @param vignetteEffect The {@link Vignette} to apply to each frame in order.
+ * @param useHdr Whether input textures come from an HDR source. If {@code true}, colors will be
+ *     in linear RGB BT.2020. If {@code false}, colors will be in linear RGB BT.709.
+ * @throws FrameProcessingException If a problem occurs while reading shader files.
+ */
+internal class VignetteProcessor(context: Context?, vignetteEffect: Vignette, useHdr: Boolean) :
+  SingleFrameGlTextureProcessor(useHdr) {
+  private var glProgram: GlProgram? = null
+  private var aspectRatio: Float = 0.0f
+
+  private val vignetteEffect: Vignette
+
+  init {
+    this.vignetteEffect = vignetteEffect
+
+    glProgram =
+      try {
+        GlProgram(context!!, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH)
+      } catch (e: IOException) {
+        throw FrameProcessingException(e)
+      } catch (e: GlUtil.GlException) {
+        throw FrameProcessingException(e)
+      }
+
+    // Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y.
+    glProgram!!.setBufferAttribute(
+      "aFramePosition",
+      GlUtil.getNormalizedCoordinateBounds(),
+      GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE
+    )
+    val identityMatrix = GlUtil.create4x4IdentityMatrix()
+    glProgram!!.setFloatsUniform("uTransformationMatrix", identityMatrix)
+    glProgram!!.setFloatsUniform("uTexTransformationMatrix", identityMatrix)
+  }
+
+  override fun configure(inputWidth: Int, inputHeight: Int): Pair<Int, Int> {
+    this.aspectRatio = inputWidth.toFloat() / inputHeight.toFloat()
+
+    return Pair.create(inputWidth, inputHeight)
+  }
+
+  @Throws(FrameProcessingException::class)
+  override fun drawFrame(inputTexId: Int, presentationTimeUs: Long) {
+    try {
+      glProgram!!.use()
+      // Set the various uniform right before rendering.  This allows values to
+      // be changed or interpolated between frames.
+
+      val shouldVignetteAlpha = this.vignetteEffect.vignetteStyle == Vignette.VignetteStyle.ALPHA ||
+          this.vignetteEffect.vignetteStyle == Vignette.VignetteStyle.COLOR_AND_ALPHA
+      val shouldVignetteColor = this.vignetteEffect.vignetteStyle == Vignette.VignetteStyle.COLOR ||
+          this.vignetteEffect.vignetteStyle == Vignette.VignetteStyle.COLOR_AND_ALPHA
+      glProgram!!.setFloatUniform("uAspectRatio", this.aspectRatio)
+      glProgram!!.setFloatUniform("uInnerRadius", this.vignetteEffect.innerRadius)
+      glProgram!!.setFloatUniform("uOuterRadius", this.vignetteEffect.outerRadius)
+      glProgram!!.setIntUniform("uShouldVignetteAlpha",
+        if (shouldVignetteAlpha) 1 else 0)
+      glProgram!!.setIntUniform("uShouldVignetteColor",
+        if (shouldVignetteColor) 1 else 0)
+      glProgram!!.setSamplerTexIdUniform("uTexSampler", inputTexId, /* texUnitIndex= */ 0)
+      glProgram!!.bindAttributesAndUniforms()
+
+      // The four-vertex triangle strip forms a quad.
+      GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4)
+    } catch (e: GlUtil.GlException) {
+      throw FrameProcessingException(e, presentationTimeUs)
+    }
+  }
+
+  @Throws(FrameProcessingException::class)
+  override fun release() {
+    super.release()
+    try {
+      glProgram!!.delete()
+    } catch (e: GlUtil.GlException) {
+      throw FrameProcessingException(e)
+    }
+  }
+
+  companion object {
+    private const val VERTEX_SHADER_PATH = "shaders/vertex_shader_transformation_es2.glsl"
+    private const val FRAGMENT_SHADER_PATH = "shaders/fragment_shader_vignette_es2.glsl"
+  }
+}
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/GLFrontBufferedRendererTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/GLFrontBufferedRendererTest.kt
index 5ccbdf2..7e76d32 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/GLFrontBufferedRendererTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/GLFrontBufferedRendererTest.kt
@@ -41,6 +41,7 @@
 import org.junit.Assert.assertNotEquals
 import org.junit.Assert.assertTrue
 import org.junit.Assert.fail
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -52,6 +53,7 @@
         val TAG = "GLFrontBufferedRenderer"
     }
 
+    @Ignore("b/262909049")
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
     @Test
     fun testFrontBufferedLayerRender() {
@@ -531,6 +533,7 @@
         }
     }
 
+    @Ignore("b/262909049")
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
     @Test
     fun testRenderAfterPauseAndResume() {
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlCompatTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlCompatTest.kt
index ab28109..2da652a 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlCompatTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/surface/SurfaceControlCompatTest.kt
@@ -45,6 +45,7 @@
 import org.junit.Assert.assertTrue
 import org.junit.Assert.fail
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -532,7 +533,8 @@
     }
 
     @Test
-    @SdkSuppress(maxSdkVersion = 33) // b/262909049: Failing on SDK 34
+    @Ignore("b/262903415")
+    @SdkSuppress(minSdkVersion = 29, maxSdkVersion = 33) // b/262909049: Failing on SDK 34
     fun testTransactionSetBuffer_singleReleaseCallback() {
         val releaseLatch = CountDownLatch(1)
         val scenario = ActivityScenario.launch(SurfaceControlWrapperTestActivity::class.java)
@@ -591,8 +593,9 @@
         }
     }
 
+    @Ignore("b/262909049")
     @Test
-    @SdkSuppress(maxSdkVersion = 33) // b/262909049: Failing on SDK 34
+    @SdkSuppress(minSdkVersion = 29, maxSdkVersion = 33) // b/262909049: Failing on SDK 34
     fun testTransactionSetBuffer_multipleReleaseCallbacksAndOverwriteWithSingleSC() {
         val releaseLatch = CountDownLatch(1)
         val releaseLatch2 = CountDownLatch(1)
@@ -735,6 +738,7 @@
         }
     }
 
+    @Ignore("b/262909049")
     @Test
     fun testTransactionSetBuffer_ReleaseCallbacksAndOverwriteWithMultipleSC() {
         val releaseLatch = CountDownLatch(1)
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/datatype/RecordsTypeNameMap.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/datatype/RecordsTypeNameMap.kt
index 9921161..547c584 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/datatype/RecordsTypeNameMap.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/datatype/RecordsTypeNameMap.kt
@@ -34,17 +34,8 @@
 import androidx.health.connect.client.records.ExerciseSessionRecord
 import androidx.health.connect.client.records.FloorsClimbedRecord
 import androidx.health.connect.client.records.HeartRateRecord
-import androidx.health.connect.client.records.HeartRateVariabilityDifferentialIndexRecord
 import androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySd2Record
-import androidx.health.connect.client.records.HeartRateVariabilitySdannRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySdnnIndexRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySdnnRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySdsdRecord
-import androidx.health.connect.client.records.HeartRateVariabilityTinnRecord
 import androidx.health.connect.client.records.HeightRecord
-import androidx.health.connect.client.records.HipCircumferenceRecord
 import androidx.health.connect.client.records.HydrationRecord
 import androidx.health.connect.client.records.IntermenstrualBleedingRecord
 import androidx.health.connect.client.records.LeanBodyMassRecord
@@ -65,7 +56,6 @@
 import androidx.health.connect.client.records.StepsRecord
 import androidx.health.connect.client.records.TotalCaloriesBurnedRecord
 import androidx.health.connect.client.records.Vo2MaxRecord
-import androidx.health.connect.client.records.WaistCircumferenceRecord
 import androidx.health.connect.client.records.WeightRecord
 import androidx.health.connect.client.records.WheelchairPushesRecord
 import kotlin.reflect.KClass
@@ -89,18 +79,8 @@
         "ElevationGained" to ElevationGainedRecord::class,
         "FloorsClimbed" to FloorsClimbedRecord::class,
         "HeartRateSeries" to HeartRateRecord::class, // Keep legacy Series suffix
-        "HeartRateVariabilityDifferentialIndex" to
-            HeartRateVariabilityDifferentialIndexRecord::class,
         "HeartRateVariabilityRmssd" to HeartRateVariabilityRmssdRecord::class,
-        "HeartRateVariabilityS" to HeartRateVariabilitySRecord::class,
-        "HeartRateVariabilitySd2" to HeartRateVariabilitySd2Record::class,
-        "HeartRateVariabilitySdann" to HeartRateVariabilitySdannRecord::class,
-        "HeartRateVariabilitySdnn" to HeartRateVariabilitySdnnRecord::class,
-        "HeartRateVariabilitySdnnIndex" to HeartRateVariabilitySdnnIndexRecord::class,
-        "HeartRateVariabilitySdsd" to HeartRateVariabilitySdsdRecord::class,
-        "HeartRateVariabilityTinn" to HeartRateVariabilityTinnRecord::class,
         "Height" to HeightRecord::class,
-        "HipCircumference" to HipCircumferenceRecord::class,
         "Hydration" to HydrationRecord::class,
         "LeanBodyMass" to LeanBodyMassRecord::class,
         "Menstruation" to MenstruationFlowRecord::class,
@@ -120,7 +100,6 @@
         "StepsCadenceSeries" to StepsCadenceRecord::class, // Keep legacy Series suffix
         "TotalCaloriesBurned" to TotalCaloriesBurnedRecord::class,
         "Vo2Max" to Vo2MaxRecord::class,
-        "WaistCircumference" to WaistCircumferenceRecord::class,
         "WheelchairPushes" to WheelchairPushesRecord::class,
         "Weight" to WeightRecord::class,
     )
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
index 2e5f33d..31e6a1c 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordConverters.kt
@@ -37,17 +37,8 @@
 import androidx.health.connect.client.records.ExerciseSessionRecord
 import androidx.health.connect.client.records.FloorsClimbedRecord
 import androidx.health.connect.client.records.HeartRateRecord
-import androidx.health.connect.client.records.HeartRateVariabilityDifferentialIndexRecord
 import androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySd2Record
-import androidx.health.connect.client.records.HeartRateVariabilitySdannRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySdnnIndexRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySdnnRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySdsdRecord
-import androidx.health.connect.client.records.HeartRateVariabilityTinnRecord
 import androidx.health.connect.client.records.HeightRecord
-import androidx.health.connect.client.records.HipCircumferenceRecord
 import androidx.health.connect.client.records.HydrationRecord
 import androidx.health.connect.client.records.IntermenstrualBleedingRecord
 import androidx.health.connect.client.records.LeanBodyMassRecord
@@ -69,7 +60,6 @@
 import androidx.health.connect.client.records.StepsRecord
 import androidx.health.connect.client.records.TotalCaloriesBurnedRecord
 import androidx.health.connect.client.records.Vo2MaxRecord
-import androidx.health.connect.client.records.WaistCircumferenceRecord
 import androidx.health.connect.client.records.WeightRecord
 import androidx.health.connect.client.records.WheelchairPushesRecord
 import androidx.health.connect.client.units.BloodGlucose
@@ -247,20 +237,6 @@
                     zoneOffset = zoneOffset,
                     metadata = metadata
                 )
-            "HipCircumference" ->
-                HipCircumferenceRecord(
-                    circumference = getDouble("circumference").meters,
-                    time = time,
-                    zoneOffset = zoneOffset,
-                    metadata = metadata
-                )
-            "HeartRateVariabilityDifferentialIndex" ->
-                HeartRateVariabilityDifferentialIndexRecord(
-                    heartRateVariabilityMillis = getDouble("heartRateVariability"),
-                    time = time,
-                    zoneOffset = zoneOffset,
-                    metadata = metadata
-                )
             "HeartRateVariabilityRmssd" ->
                 HeartRateVariabilityRmssdRecord(
                     heartRateVariabilityMillis = getDouble("heartRateVariability"),
@@ -268,55 +244,6 @@
                     zoneOffset = zoneOffset,
                     metadata = metadata
                 )
-            "HeartRateVariabilityS" ->
-                HeartRateVariabilitySRecord(
-                    heartRateVariabilityMillis = getDouble("heartRateVariability"),
-                    time = time,
-                    zoneOffset = zoneOffset,
-                    metadata = metadata
-                )
-            "HeartRateVariabilitySd2" ->
-                HeartRateVariabilitySd2Record(
-                    heartRateVariabilityMillis = getDouble("heartRateVariability"),
-                    time = time,
-                    zoneOffset = zoneOffset,
-                    metadata = metadata
-                )
-            "HeartRateVariabilitySdann" ->
-                HeartRateVariabilitySdannRecord(
-                    heartRateVariabilityMillis = getDouble("heartRateVariability"),
-                    time = time,
-                    zoneOffset = zoneOffset,
-                    metadata = metadata
-                )
-            "HeartRateVariabilitySdnnIndex" ->
-                HeartRateVariabilitySdnnIndexRecord(
-                    heartRateVariabilityMillis = getDouble("heartRateVariability"),
-                    time = time,
-                    zoneOffset = zoneOffset,
-                    metadata = metadata
-                )
-            "HeartRateVariabilitySdnn" ->
-                HeartRateVariabilitySdnnRecord(
-                    heartRateVariabilityMillis = getDouble("heartRateVariability"),
-                    time = time,
-                    zoneOffset = zoneOffset,
-                    metadata = metadata
-                )
-            "HeartRateVariabilitySdsd" ->
-                HeartRateVariabilitySdsdRecord(
-                    heartRateVariabilityMillis = getDouble("heartRateVariability"),
-                    time = time,
-                    zoneOffset = zoneOffset,
-                    metadata = metadata
-                )
-            "HeartRateVariabilityTinn" ->
-                HeartRateVariabilityTinnRecord(
-                    heartRateVariabilityMillis = getDouble("heartRateVariability"),
-                    time = time,
-                    zoneOffset = zoneOffset,
-                    metadata = metadata
-                )
             "LeanBodyMass" ->
                 LeanBodyMassRecord(
                     mass = getDouble("mass").kilograms,
@@ -447,13 +374,6 @@
                     zoneOffset = zoneOffset,
                     metadata = metadata
                 )
-            "WaistCircumference" ->
-                WaistCircumferenceRecord(
-                    circumference = getDouble("circumference").meters,
-                    time = time,
-                    zoneOffset = zoneOffset,
-                    metadata = metadata
-                )
             "Weight" ->
                 WeightRecord(
                     weight = getDouble("weight").kilograms,
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt
index 06da716..f87edca 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoConverters.kt
@@ -37,17 +37,8 @@
 import androidx.health.connect.client.records.ExerciseSessionRecord
 import androidx.health.connect.client.records.FloorsClimbedRecord
 import androidx.health.connect.client.records.HeartRateRecord
-import androidx.health.connect.client.records.HeartRateVariabilityDifferentialIndexRecord
 import androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySd2Record
-import androidx.health.connect.client.records.HeartRateVariabilitySdannRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySdnnIndexRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySdnnRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySdsdRecord
-import androidx.health.connect.client.records.HeartRateVariabilityTinnRecord
 import androidx.health.connect.client.records.HeightRecord
-import androidx.health.connect.client.records.HipCircumferenceRecord
 import androidx.health.connect.client.records.HydrationRecord
 import androidx.health.connect.client.records.IntermenstrualBleedingRecord
 import androidx.health.connect.client.records.LeanBodyMassRecord
@@ -71,7 +62,6 @@
 import androidx.health.connect.client.records.StepsRecord
 import androidx.health.connect.client.records.TotalCaloriesBurnedRecord
 import androidx.health.connect.client.records.Vo2MaxRecord
-import androidx.health.connect.client.records.WaistCircumferenceRecord
 import androidx.health.connect.client.records.WeightRecord
 import androidx.health.connect.client.records.WheelchairPushesRecord
 import androidx.health.platform.client.proto.DataProto
@@ -194,56 +184,11 @@
                 .setDataType(protoDataType("Height"))
                 .apply { putValues("height", doubleVal(height.inMeters)) }
                 .build()
-        is HipCircumferenceRecord ->
-            instantaneousProto()
-                .setDataType(protoDataType("HipCircumference"))
-                .apply { putValues("circumference", doubleVal(circumference.inMeters)) }
-                .build()
-        is HeartRateVariabilityDifferentialIndexRecord ->
-            instantaneousProto()
-                .setDataType(protoDataType("HeartRateVariabilityDifferentialIndex"))
-                .apply { putValues("heartRateVariability", doubleVal(heartRateVariabilityMillis)) }
-                .build()
         is HeartRateVariabilityRmssdRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("HeartRateVariabilityRmssd"))
                 .apply { putValues("heartRateVariability", doubleVal(heartRateVariabilityMillis)) }
                 .build()
-        is HeartRateVariabilitySRecord ->
-            instantaneousProto()
-                .setDataType(protoDataType("HeartRateVariabilityS"))
-                .apply { putValues("heartRateVariability", doubleVal(heartRateVariabilityMillis)) }
-                .build()
-        is HeartRateVariabilitySd2Record ->
-            instantaneousProto()
-                .setDataType(protoDataType("HeartRateVariabilitySd2"))
-                .apply { putValues("heartRateVariability", doubleVal(heartRateVariabilityMillis)) }
-                .build()
-        is HeartRateVariabilitySdannRecord ->
-            instantaneousProto()
-                .setDataType(protoDataType("HeartRateVariabilitySdann"))
-                .apply { putValues("heartRateVariability", doubleVal(heartRateVariabilityMillis)) }
-                .build()
-        is HeartRateVariabilitySdnnIndexRecord ->
-            instantaneousProto()
-                .setDataType(protoDataType("HeartRateVariabilitySdnnIndex"))
-                .apply { putValues("heartRateVariability", doubleVal(heartRateVariabilityMillis)) }
-                .build()
-        is HeartRateVariabilitySdnnRecord ->
-            instantaneousProto()
-                .setDataType(protoDataType("HeartRateVariabilitySdnn"))
-                .apply { putValues("heartRateVariability", doubleVal(heartRateVariabilityMillis)) }
-                .build()
-        is HeartRateVariabilitySdsdRecord ->
-            instantaneousProto()
-                .setDataType(protoDataType("HeartRateVariabilitySdsd"))
-                .apply { putValues("heartRateVariability", doubleVal(heartRateVariabilityMillis)) }
-                .build()
-        is HeartRateVariabilityTinnRecord ->
-            instantaneousProto()
-                .setDataType(protoDataType("HeartRateVariabilityTinn"))
-                .apply { putValues("heartRateVariability", doubleVal(heartRateVariabilityMillis)) }
-                .build()
         is IntermenstrualBleedingRecord ->
             instantaneousProto().setDataType(protoDataType("IntermenstrualBleeding")).build()
         is LeanBodyMassRecord ->
@@ -261,9 +206,7 @@
                 }
                 .build()
         is MenstruationPeriodRecord ->
-            intervalProto()
-                .setDataType(protoDataType("MenstruationPeriod"))
-                .build()
+            intervalProto().setDataType(protoDataType("MenstruationPeriod")).build()
         is OvulationTestRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("OvulationTest"))
@@ -332,11 +275,6 @@
                         ?.let { putValues("measurementMethod", it) }
                 }
                 .build()
-        is WaistCircumferenceRecord ->
-            instantaneousProto()
-                .setDataType(protoDataType("WaistCircumference"))
-                .apply { putValues("circumference", doubleVal(circumference.inMeters)) }
-                .build()
         is WeightRecord ->
             instantaneousProto()
                 .setDataType(protoDataType("Weight"))
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthPermission.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthPermission.kt
index af12b25..463f45f 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthPermission.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/permission/HealthPermission.kt
@@ -32,17 +32,8 @@
 import androidx.health.connect.client.records.ExerciseSessionRecord
 import androidx.health.connect.client.records.FloorsClimbedRecord
 import androidx.health.connect.client.records.HeartRateRecord
-import androidx.health.connect.client.records.HeartRateVariabilityDifferentialIndexRecord
 import androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySd2Record
-import androidx.health.connect.client.records.HeartRateVariabilitySdannRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySdnnIndexRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySdnnRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySdsdRecord
-import androidx.health.connect.client.records.HeartRateVariabilityTinnRecord
 import androidx.health.connect.client.records.HeightRecord
-import androidx.health.connect.client.records.HipCircumferenceRecord
 import androidx.health.connect.client.records.HydrationRecord
 import androidx.health.connect.client.records.IntermenstrualBleedingRecord
 import androidx.health.connect.client.records.LeanBodyMassRecord
@@ -63,7 +54,6 @@
 import androidx.health.connect.client.records.StepsRecord
 import androidx.health.connect.client.records.TotalCaloriesBurnedRecord
 import androidx.health.connect.client.records.Vo2MaxRecord
-import androidx.health.connect.client.records.WaistCircumferenceRecord
 import androidx.health.connect.client.records.WeightRecord
 import androidx.health.connect.client.records.WheelchairPushesRecord
 import kotlin.reflect.KClass
@@ -301,27 +291,9 @@
                 FloorsClimbedRecord::class to
                     READ_FLOORS_CLIMBED.substringAfter(READ_PERMISSION_PREFIX),
                 HeartRateRecord::class to READ_HEART_RATE.substringAfter(READ_PERMISSION_PREFIX),
-                HeartRateVariabilityDifferentialIndexRecord::class to
-                    READ_HEART_RATE_VARIABILITY.substringAfter(READ_PERMISSION_PREFIX),
                 HeartRateVariabilityRmssdRecord::class to
                     READ_HEART_RATE_VARIABILITY.substringAfter(READ_PERMISSION_PREFIX),
-                HeartRateVariabilitySd2Record::class to
-                    READ_HEART_RATE_VARIABILITY.substringAfter(READ_PERMISSION_PREFIX),
-                HeartRateVariabilitySdannRecord::class to
-                    READ_HEART_RATE_VARIABILITY.substringAfter(READ_PERMISSION_PREFIX),
-                HeartRateVariabilitySdnnIndexRecord::class to
-                    READ_HEART_RATE_VARIABILITY.substringAfter(READ_PERMISSION_PREFIX),
-                HeartRateVariabilitySdnnRecord::class to
-                    READ_HEART_RATE_VARIABILITY.substringAfter(READ_PERMISSION_PREFIX),
-                HeartRateVariabilitySdsdRecord::class to
-                    READ_HEART_RATE_VARIABILITY.substringAfter(READ_PERMISSION_PREFIX),
-                HeartRateVariabilitySRecord::class to
-                    READ_HEART_RATE_VARIABILITY.substringAfter(READ_PERMISSION_PREFIX),
-                HeartRateVariabilityTinnRecord::class to
-                    READ_HEART_RATE_VARIABILITY.substringAfter(READ_PERMISSION_PREFIX),
                 HeightRecord::class to READ_HEIGHT.substringAfter(READ_PERMISSION_PREFIX),
-                HipCircumferenceRecord::class to
-                    READ_HIP_CIRCUMFERENCE.substringAfter(READ_PERMISSION_PREFIX),
                 HydrationRecord::class to READ_HYDRATION.substringAfter(READ_PERMISSION_PREFIX),
                 IntermenstrualBleedingRecord::class to
                     READ_INTERMENSTRUAL_BLEEDING.substringAfter(READ_PERMISSION_PREFIX),
@@ -351,8 +323,6 @@
                 TotalCaloriesBurnedRecord::class to
                     READ_TOTAL_CALORIES_BURNED.substringAfter(READ_PERMISSION_PREFIX),
                 Vo2MaxRecord::class to READ_VO2_MAX.substringAfter(READ_PERMISSION_PREFIX),
-                WaistCircumferenceRecord::class to
-                    READ_WAIST_CIRCUMFERENCE.substringAfter(READ_PERMISSION_PREFIX),
                 WeightRecord::class to READ_WEIGHT.substringAfter(READ_PERMISSION_PREFIX),
                 WheelchairPushesRecord::class to
                     READ_WHEELCHAIR_PUSHES.substringAfter(READ_PERMISSION_PREFIX),
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilityDifferentialIndexRecord.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilityDifferentialIndexRecord.kt
deleted file mode 100644
index 3ca9306..0000000
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilityDifferentialIndexRecord.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.health.connect.client.records
-
-import androidx.annotation.RestrictTo
-import androidx.health.connect.client.records.metadata.Metadata
-import java.time.Instant
-import java.time.ZoneOffset
-
-/**
- * Captures user's heart rate variability (HRV) measured as the difference between the widths of the
- * histogram of differences between adjacent heart beats measured at selected heights.
- *
- * @suppress
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public class HeartRateVariabilityDifferentialIndexRecord(
-    override val time: Instant,
-    override val zoneOffset: ZoneOffset?,
-    /** Heart rate variability in milliseconds. Required field. */
-    public val heartRateVariabilityMillis: Double,
-    override val metadata: Metadata = Metadata.EMPTY,
-) : InstantaneousRecord {
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is HeartRateVariabilityDifferentialIndexRecord) return false
-
-        if (heartRateVariabilityMillis != other.heartRateVariabilityMillis) return false
-        if (time != other.time) return false
-        if (zoneOffset != other.zoneOffset) return false
-        if (metadata != other.metadata) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = 0
-        result = 31 * result + heartRateVariabilityMillis.hashCode()
-        result = 31 * result + time.hashCode()
-        result = 31 * result + (zoneOffset?.hashCode() ?: 0)
-        result = 31 * result + metadata.hashCode()
-        return result
-    }
-}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilitySRecord.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilitySRecord.kt
deleted file mode 100644
index 168870a..0000000
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilitySRecord.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.health.connect.client.records
-
-import androidx.annotation.RestrictTo
-import androidx.health.connect.client.records.metadata.Metadata
-import java.time.Instant
-import java.time.ZoneOffset
-
-/**
- * Captures user's heart rate variability (HRV) as measured by the area of the elipse on a Poincare
- * plot.
- *
- * @suppress
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public class HeartRateVariabilitySRecord(
-    override val time: Instant,
-    override val zoneOffset: ZoneOffset?,
-    /** Heart rate variability in milliseconds. Required field. */
-    public val heartRateVariabilityMillis: Double,
-    override val metadata: Metadata = Metadata.EMPTY,
-) : InstantaneousRecord {
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is HeartRateVariabilitySRecord) return false
-
-        if (heartRateVariabilityMillis != other.heartRateVariabilityMillis) return false
-        if (time != other.time) return false
-        if (zoneOffset != other.zoneOffset) return false
-        if (metadata != other.metadata) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = 0
-        result = 31 * result + heartRateVariabilityMillis.hashCode()
-        result = 31 * result + time.hashCode()
-        result = 31 * result + (zoneOffset?.hashCode() ?: 0)
-        result = 31 * result + metadata.hashCode()
-        return result
-    }
-}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilitySd2Record.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilitySd2Record.kt
deleted file mode 100644
index 6e19d5d..0000000
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilitySd2Record.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.health.connect.client.records
-
-import androidx.annotation.RestrictTo
-import androidx.health.connect.client.records.metadata.Metadata
-import java.time.Instant
-import java.time.ZoneOffset
-
-/**
- * Captures user's heart rate variability (HRV) as measured by the Poincaré plot standard deviation
- * along the line of identity.
- *
- * @suppress
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public class HeartRateVariabilitySd2Record(
-    override val time: Instant,
-    override val zoneOffset: ZoneOffset?,
-    /** Heart rate variability in milliseconds. Required field. */
-    public val heartRateVariabilityMillis: Double,
-    override val metadata: Metadata = Metadata.EMPTY,
-) : InstantaneousRecord {
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is HeartRateVariabilitySd2Record) return false
-
-        if (heartRateVariabilityMillis != other.heartRateVariabilityMillis) return false
-        if (time != other.time) return false
-        if (zoneOffset != other.zoneOffset) return false
-        if (metadata != other.metadata) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = 0
-        result = 31 * result + heartRateVariabilityMillis.hashCode()
-        result = 31 * result + time.hashCode()
-        result = 31 * result + (zoneOffset?.hashCode() ?: 0)
-        result = 31 * result + metadata.hashCode()
-        return result
-    }
-}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilitySdannRecord.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilitySdannRecord.kt
deleted file mode 100644
index 6d959c1..0000000
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilitySdannRecord.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.health.connect.client.records
-
-import androidx.annotation.RestrictTo
-import androidx.health.connect.client.records.metadata.Metadata
-import java.time.Instant
-import java.time.ZoneOffset
-
-/**
- * Captures user's heart rate variability (HRV) as measured by the standard deviation of the
- * averages of NN intervals in all 5-minute segments of the entire recording.
- *
- * @suppress
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public class HeartRateVariabilitySdannRecord(
-    override val time: Instant,
-    override val zoneOffset: ZoneOffset?,
-    /** Heart rate variability in milliseconds. Required field. */
-    public val heartRateVariabilityMillis: Double,
-    override val metadata: Metadata = Metadata.EMPTY,
-) : InstantaneousRecord {
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is HeartRateVariabilitySdannRecord) return false
-
-        if (heartRateVariabilityMillis != other.heartRateVariabilityMillis) return false
-        if (time != other.time) return false
-        if (zoneOffset != other.zoneOffset) return false
-        if (metadata != other.metadata) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = 0
-        result = 31 * result + heartRateVariabilityMillis.hashCode()
-        result = 31 * result + time.hashCode()
-        result = 31 * result + (zoneOffset?.hashCode() ?: 0)
-        result = 31 * result + metadata.hashCode()
-        return result
-    }
-}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilitySdnnIndexRecord.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilitySdnnIndexRecord.kt
deleted file mode 100644
index c607428..0000000
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilitySdnnIndexRecord.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.health.connect.client.records
-
-import androidx.annotation.RestrictTo
-import androidx.health.connect.client.records.metadata.Metadata
-import java.time.Instant
-import java.time.ZoneOffset
-
-/**
- * Captures user's heart rate variability (HRV) as measured by the mean of the standard deviations
- * of all NN intervals for all of the recording.
- *
- * @suppress
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public class HeartRateVariabilitySdnnIndexRecord(
-    override val time: Instant,
-    override val zoneOffset: ZoneOffset?,
-    /** Heart rate variability in milliseconds. Required field. */
-    public val heartRateVariabilityMillis: Double,
-    override val metadata: Metadata = Metadata.EMPTY,
-) : InstantaneousRecord {
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is HeartRateVariabilitySdnnIndexRecord) return false
-
-        if (heartRateVariabilityMillis != other.heartRateVariabilityMillis) return false
-        if (time != other.time) return false
-        if (zoneOffset != other.zoneOffset) return false
-        if (metadata != other.metadata) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = 0
-        result = 31 * result + heartRateVariabilityMillis.hashCode()
-        result = 31 * result + time.hashCode()
-        result = 31 * result + (zoneOffset?.hashCode() ?: 0)
-        result = 31 * result + metadata.hashCode()
-        return result
-    }
-}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilitySdnnRecord.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilitySdnnRecord.kt
deleted file mode 100644
index 39732cf..0000000
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilitySdnnRecord.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.health.connect.client.records
-
-import androidx.annotation.RestrictTo
-import androidx.health.connect.client.records.metadata.Metadata
-import java.time.Instant
-import java.time.ZoneOffset
-
-/**
- * Captures user's heart rate variability (HRV) as measured by the standard deviation of all N-N
- * intervals.
- *
- * @suppress
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public class HeartRateVariabilitySdnnRecord(
-    override val time: Instant,
-    override val zoneOffset: ZoneOffset?,
-    /** Heart rate variability in milliseconds. Required field. */
-    public val heartRateVariabilityMillis: Double,
-    override val metadata: Metadata = Metadata.EMPTY,
-) : InstantaneousRecord {
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is HeartRateVariabilitySdnnRecord) return false
-
-        if (heartRateVariabilityMillis != other.heartRateVariabilityMillis) return false
-        if (time != other.time) return false
-        if (zoneOffset != other.zoneOffset) return false
-        if (metadata != other.metadata) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = 0
-        result = 31 * result + heartRateVariabilityMillis.hashCode()
-        result = 31 * result + time.hashCode()
-        result = 31 * result + (zoneOffset?.hashCode() ?: 0)
-        result = 31 * result + metadata.hashCode()
-        return result
-    }
-}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilitySdsdRecord.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilitySdsdRecord.kt
deleted file mode 100644
index 76bf706..0000000
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilitySdsdRecord.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.health.connect.client.records
-
-import androidx.annotation.RestrictTo
-import androidx.health.connect.client.records.metadata.Metadata
-import java.time.Instant
-import java.time.ZoneOffset
-
-/**
- * Captures user's heart rate variability (HRV) as measured by the standard deviation of differences
- * between adjacent NN intervals.
- *
- * @suppress
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public class HeartRateVariabilitySdsdRecord(
-    override val time: Instant,
-    override val zoneOffset: ZoneOffset?,
-    /** Heart rate variability in milliseconds. Required field. */
-    public val heartRateVariabilityMillis: Double,
-    override val metadata: Metadata = Metadata.EMPTY,
-) : InstantaneousRecord {
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is HeartRateVariabilitySdsdRecord) return false
-
-        if (heartRateVariabilityMillis != other.heartRateVariabilityMillis) return false
-        if (time != other.time) return false
-        if (zoneOffset != other.zoneOffset) return false
-        if (metadata != other.metadata) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = 0
-        result = 31 * result + heartRateVariabilityMillis.hashCode()
-        result = 31 * result + time.hashCode()
-        result = 31 * result + (zoneOffset?.hashCode() ?: 0)
-        result = 31 * result + metadata.hashCode()
-        return result
-    }
-}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilityTinnRecord.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilityTinnRecord.kt
deleted file mode 100644
index 6bf3bac..0000000
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HeartRateVariabilityTinnRecord.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.health.connect.client.records
-
-import androidx.annotation.RestrictTo
-import androidx.health.connect.client.records.metadata.Metadata
-import java.time.Instant
-import java.time.ZoneOffset
-
-/**
- * Captures user's heart rate variability (HRV) as measured by the Triangular Interpolation
- * (baseline width) of a histogram displaying NN intervals.
- *
- * @suppress
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public class HeartRateVariabilityTinnRecord(
-    override val time: Instant,
-    override val zoneOffset: ZoneOffset?,
-    /** Heart rate variability in milliseconds. Required field. */
-    public val heartRateVariabilityMillis: Double,
-    override val metadata: Metadata = Metadata.EMPTY,
-) : InstantaneousRecord {
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is HeartRateVariabilityTinnRecord) return false
-
-        if (heartRateVariabilityMillis != other.heartRateVariabilityMillis) return false
-        if (time != other.time) return false
-        if (zoneOffset != other.zoneOffset) return false
-        if (metadata != other.metadata) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = 0
-        result = 31 * result + heartRateVariabilityMillis.hashCode()
-        result = 31 * result + time.hashCode()
-        result = 31 * result + (zoneOffset?.hashCode() ?: 0)
-        result = 31 * result + metadata.hashCode()
-        return result
-    }
-}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HipCircumferenceRecord.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HipCircumferenceRecord.kt
deleted file mode 100644
index bda2aaf..0000000
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/HipCircumferenceRecord.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.health.connect.client.records
-
-import androidx.annotation.RestrictTo
-import androidx.health.connect.client.records.metadata.Metadata
-import androidx.health.connect.client.units.Length
-import androidx.health.connect.client.units.meters
-import java.time.Instant
-import java.time.ZoneOffset
-
-/** Captures the user's hip circumference. */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public class HipCircumferenceRecord(
-    override val time: Instant,
-    override val zoneOffset: ZoneOffset?,
-    /** Circumference in [Length] unit. Required field. Valid range: 0-10 meters. */
-    public val circumference: Length,
-    override val metadata: Metadata = Metadata.EMPTY,
-) : InstantaneousRecord {
-
-    init {
-        circumference.requireNotLess(other = circumference.zero(), name = "circumference")
-        circumference.requireNotMore(other = MAX_CIRCUMFERENCE, name = "circumference")
-    }
-
-    /*
-     * Generated by the IDE: Code -> Generate -> "equals() and hashCode()".
-     */
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is HipCircumferenceRecord) return false
-
-        if (circumference != other.circumference) return false
-        if (time != other.time) return false
-        if (zoneOffset != other.zoneOffset) return false
-        if (metadata != other.metadata) return false
-
-        return true
-    }
-
-    /*
-     * Generated by the IDE: Code -> Generate -> "equals() and hashCode()".
-     */
-    override fun hashCode(): Int {
-        var result = circumference.hashCode()
-        result = 31 * result + time.hashCode()
-        result = 31 * result + (zoneOffset?.hashCode() ?: 0)
-        result = 31 * result + metadata.hashCode()
-        return result
-    }
-
-    private companion object {
-        private val MAX_CIRCUMFERENCE = 10.meters
-    }
-}
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/WaistCircumferenceRecord.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/WaistCircumferenceRecord.kt
deleted file mode 100644
index c8e6244..0000000
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/WaistCircumferenceRecord.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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.health.connect.client.records
-
-import androidx.annotation.RestrictTo
-import androidx.health.connect.client.records.metadata.Metadata
-import androidx.health.connect.client.units.Length
-import androidx.health.connect.client.units.meters
-import java.time.Instant
-import java.time.ZoneOffset
-
-/**
- * Captures the user's waist circumference.
- *
- * See [Length] for supported units.
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public class WaistCircumferenceRecord(
-    override val time: Instant,
-    override val zoneOffset: ZoneOffset?,
-    /** Circumference in [Length] unit. Required field. Valid range: 0-10 meters. */
-    public val circumference: Length,
-    override val metadata: Metadata = Metadata.EMPTY,
-) : InstantaneousRecord {
-
-    init {
-        circumference.requireNotLess(other = circumference.zero(), name = "circumference")
-        circumference.requireNotMore(other = MAX_CIRCUMFERENCE, name = "circumference")
-    }
-
-    /*
-     * Generated by the IDE: Code -> Generate -> "equals() and hashCode()".
-     */
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is WaistCircumferenceRecord) return false
-
-        if (circumference != other.circumference) return false
-        if (time != other.time) return false
-        if (zoneOffset != other.zoneOffset) return false
-        if (metadata != other.metadata) return false
-
-        return true
-    }
-
-    /*
-     * Generated by the IDE: Code -> Generate -> "equals() and hashCode()".
-     */
-    override fun hashCode(): Int {
-        var result = circumference.hashCode()
-        result = 31 * result + time.hashCode()
-        result = 31 * result + (zoneOffset?.hashCode() ?: 0)
-        result = 31 * result + metadata.hashCode()
-        return result
-    }
-
-    private companion object {
-        private val MAX_CIRCUMFERENCE = 10.meters
-    }
-}
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
index e19e8e8..8730026 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
@@ -34,17 +34,8 @@
 import androidx.health.connect.client.records.ExerciseSessionRecord.Companion.EXERCISE_TYPE_BACK_EXTENSION
 import androidx.health.connect.client.records.FloorsClimbedRecord
 import androidx.health.connect.client.records.HeartRateRecord
-import androidx.health.connect.client.records.HeartRateVariabilityDifferentialIndexRecord
 import androidx.health.connect.client.records.HeartRateVariabilityRmssdRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySd2Record
-import androidx.health.connect.client.records.HeartRateVariabilitySdannRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySdnnIndexRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySdnnRecord
-import androidx.health.connect.client.records.HeartRateVariabilitySdsdRecord
-import androidx.health.connect.client.records.HeartRateVariabilityTinnRecord
 import androidx.health.connect.client.records.HeightRecord
-import androidx.health.connect.client.records.HipCircumferenceRecord
 import androidx.health.connect.client.records.HydrationRecord
 import androidx.health.connect.client.records.IntermenstrualBleedingRecord
 import androidx.health.connect.client.records.LeanBodyMassRecord
@@ -67,7 +58,6 @@
 import androidx.health.connect.client.records.StepsRecord
 import androidx.health.connect.client.records.TotalCaloriesBurnedRecord
 import androidx.health.connect.client.records.Vo2MaxRecord
-import androidx.health.connect.client.records.WaistCircumferenceRecord
 import androidx.health.connect.client.records.WeightRecord
 import androidx.health.connect.client.records.WheelchairPushesRecord
 import androidx.health.connect.client.records.metadata.DataOrigin
@@ -323,34 +313,6 @@
     }
 
     @Test
-    fun testHipCircumference() {
-        val data =
-            HipCircumferenceRecord(
-                circumference = 1.meters,
-                time = START_TIME,
-                zoneOffset = END_ZONE_OFFSET,
-                metadata = TEST_METADATA
-            )
-
-        checkProtoAndRecordTypeNameMatch(data)
-        assertThat(toRecord(data.toProto())).isEqualTo(data)
-    }
-
-    @Test
-    fun testHeartRateVariabilityDifferentialIndex() {
-        val data =
-            HeartRateVariabilityDifferentialIndexRecord(
-                heartRateVariabilityMillis = 1.0,
-                time = START_TIME,
-                zoneOffset = END_ZONE_OFFSET,
-                metadata = TEST_METADATA
-            )
-
-        checkProtoAndRecordTypeNameMatch(data)
-        assertThat(toRecord(data.toProto())).isEqualTo(data)
-    }
-
-    @Test
     fun testHeartRateVariabilityRmssd() {
         val data =
             HeartRateVariabilityRmssdRecord(
@@ -365,104 +327,6 @@
     }
 
     @Test
-    fun testHeartRateVariabilityS() {
-        val data =
-            HeartRateVariabilitySRecord(
-                heartRateVariabilityMillis = 1.0,
-                time = START_TIME,
-                zoneOffset = END_ZONE_OFFSET,
-                metadata = TEST_METADATA
-            )
-
-        checkProtoAndRecordTypeNameMatch(data)
-        assertThat(toRecord(data.toProto())).isEqualTo(data)
-    }
-
-    @Test
-    fun testHeartRateVariabilitySd2() {
-        val data =
-            HeartRateVariabilitySd2Record(
-                heartRateVariabilityMillis = 1.0,
-                time = START_TIME,
-                zoneOffset = END_ZONE_OFFSET,
-                metadata = TEST_METADATA
-            )
-
-        checkProtoAndRecordTypeNameMatch(data)
-        assertThat(toRecord(data.toProto())).isEqualTo(data)
-    }
-
-    @Test
-    fun testHeartRateVariabilitySdann() {
-        val data =
-            HeartRateVariabilitySdannRecord(
-                heartRateVariabilityMillis = 1.0,
-                time = START_TIME,
-                zoneOffset = END_ZONE_OFFSET,
-                metadata = TEST_METADATA
-            )
-
-        checkProtoAndRecordTypeNameMatch(data)
-        assertThat(toRecord(data.toProto())).isEqualTo(data)
-    }
-
-    @Test
-    fun testHeartRateVariabilitySdnnIndex() {
-        val data =
-            HeartRateVariabilitySdnnIndexRecord(
-                heartRateVariabilityMillis = 1.0,
-                time = START_TIME,
-                zoneOffset = END_ZONE_OFFSET,
-                metadata = TEST_METADATA
-            )
-
-        checkProtoAndRecordTypeNameMatch(data)
-        assertThat(toRecord(data.toProto())).isEqualTo(data)
-    }
-
-    @Test
-    fun testHeartRateVariabilitySdnn() {
-        val data =
-            HeartRateVariabilitySdnnRecord(
-                heartRateVariabilityMillis = 1.0,
-                time = START_TIME,
-                zoneOffset = END_ZONE_OFFSET,
-                metadata = TEST_METADATA
-            )
-
-        checkProtoAndRecordTypeNameMatch(data)
-        assertThat(toRecord(data.toProto())).isEqualTo(data)
-    }
-
-    @Test
-    fun testHeartRateVariabilitySdsd() {
-        val data =
-            HeartRateVariabilitySdsdRecord(
-                heartRateVariabilityMillis = 1.0,
-                time = START_TIME,
-                zoneOffset = END_ZONE_OFFSET,
-                metadata = TEST_METADATA
-            )
-
-        checkProtoAndRecordTypeNameMatch(data)
-        assertThat(toRecord(data.toProto())).isEqualTo(data)
-    }
-
-    @Test
-    fun testHeartRateVariabilityTinn() {
-        val data =
-            HeartRateVariabilityTinnRecord(
-                heartRateVariabilityMillis = 1.0,
-                time = START_TIME,
-                zoneOffset = END_ZONE_OFFSET,
-                metadata = TEST_METADATA
-            )
-
-        checkProtoAndRecordTypeNameMatch(data)
-        assertThat(toRecord(data.toProto())).isEqualTo(data)
-    }
-
-    @Test
     fun testIntermenstrualBleeding() {
         val data =
             IntermenstrualBleedingRecord(
@@ -682,20 +546,6 @@
     }
 
     @Test
-    fun testWaistCircumference() {
-        val data =
-            WaistCircumferenceRecord(
-                circumference = 1.meters,
-                time = START_TIME,
-                zoneOffset = END_ZONE_OFFSET,
-                metadata = TEST_METADATA
-            )
-
-        checkProtoAndRecordTypeNameMatch(data)
-        assertThat(toRecord(data.toProto())).isEqualTo(data)
-    }
-
-    @Test
     fun testWeight() {
         val data =
             WeightRecord(
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/HealthPermissionTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/HealthPermissionTest.kt
index 2194522..66d7068 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/HealthPermissionTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/permission/HealthPermissionTest.kt
@@ -49,11 +49,10 @@
 
     @Test
     fun createReadPermission_everyRecord() {
-        RECORD_CLASSES
-            .forEach {
-                val permission = HealthPermission.getReadPermission(it)
-                assertThat(permission).isNotNull()
-            }
+        RECORD_CLASSES.forEach {
+            val permission = HealthPermission.getReadPermission(it)
+            assertThat(permission).isNotNull()
+        }
     }
 
     @Test
@@ -71,11 +70,10 @@
 
     @Test
     fun createWritePermission_everyRecord() {
-        RECORD_CLASSES
-            .forEach {
-                val permission = HealthPermission.getWritePermission(it)
-                assertThat(permission).isNotNull()
-            }
+        RECORD_CLASSES.forEach {
+            val permission = HealthPermission.getWritePermission(it)
+            assertThat(permission).isNotNull()
+        }
     }
 
     @Test
diff --git a/health/health-services-client/api/1.0.0-beta03.txt b/health/health-services-client/api/1.0.0-beta03.txt
new file mode 100644
index 0000000..9d0902d
--- /dev/null
+++ b/health/health-services-client/api/1.0.0-beta03.txt
@@ -0,0 +1,909 @@
+// Signature format: 4.0
+package androidx.health.services.client {
+
+  @kotlin.jvm.JvmDefaultWithCompatibility public interface ExerciseClient {
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> addGoalToActiveExerciseAsync(androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearUpdateCallbackAsync(androidx.health.services.client.ExerciseUpdateCallback callback);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> endExerciseAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> flushAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseCapabilities> getCapabilitiesAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfoAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLapAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideAutoPauseAndResumeForActiveExerciseAsync(boolean enabled);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideBatchingModesForActiveExerciseAsync(java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModes);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> pauseExerciseAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> prepareExerciseAsync(androidx.health.services.client.data.WarmUpConfig configuration);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> removeGoalFromActiveExerciseAsync(androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> resumeExerciseAsync();
+    method public void setUpdateCallback(androidx.health.services.client.ExerciseUpdateCallback callback);
+    method public void setUpdateCallback(java.util.concurrent.Executor executor, androidx.health.services.client.ExerciseUpdateCallback callback);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> startExerciseAsync(androidx.health.services.client.data.ExerciseConfig configuration);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> updateExerciseTypeConfigAsync(androidx.health.services.client.data.ExerciseTypeConfig exerciseTypeConfig);
+  }
+
+  public final class ExerciseClientExtensionKt {
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? addGoalToActiveExercise(androidx.health.services.client.ExerciseClient, androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? clearUpdateCallback(androidx.health.services.client.ExerciseClient, androidx.health.services.client.ExerciseUpdateCallback callback, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? endExercise(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? flush(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? getCapabilities(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super androidx.health.services.client.data.ExerciseCapabilities>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? getCurrentExerciseInfo(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super androidx.health.services.client.data.ExerciseInfo>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? markLap(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? overrideAutoPauseAndResumeForActiveExercise(androidx.health.services.client.ExerciseClient, boolean enabled, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? pauseExercise(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? prepareExercise(androidx.health.services.client.ExerciseClient, androidx.health.services.client.data.WarmUpConfig configuration, kotlin.coroutines.Continuation<? super kotlin.Unit>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? removeGoalFromActiveExercise(androidx.health.services.client.ExerciseClient, androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? resumeExercise(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? startExercise(androidx.health.services.client.ExerciseClient, androidx.health.services.client.data.ExerciseConfig configuration, kotlin.coroutines.Continuation<? super kotlin.Unit>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? updateExerciseTypeConfig(androidx.health.services.client.ExerciseClient, androidx.health.services.client.data.ExerciseTypeConfig exerciseTypeConfig, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+  }
+
+  public interface ExerciseUpdateCallback {
+    method public void onAvailabilityChanged(androidx.health.services.client.data.DataType<?,?> dataType, androidx.health.services.client.data.Availability availability);
+    method public void onExerciseUpdateReceived(androidx.health.services.client.data.ExerciseUpdate update);
+    method public void onLapSummaryReceived(androidx.health.services.client.data.ExerciseLapSummary lapSummary);
+    method public void onRegistered();
+    method public void onRegistrationFailed(Throwable throwable);
+  }
+
+  public final class HealthServices {
+    method public static androidx.health.services.client.HealthServicesClient getClient(android.content.Context context);
+    field public static final androidx.health.services.client.HealthServices INSTANCE;
+  }
+
+  public interface HealthServicesClient {
+    method public androidx.health.services.client.ExerciseClient getExerciseClient();
+    method public androidx.health.services.client.MeasureClient getMeasureClient();
+    method public androidx.health.services.client.PassiveMonitoringClient getPassiveMonitoringClient();
+    property public abstract androidx.health.services.client.ExerciseClient exerciseClient;
+    property public abstract androidx.health.services.client.MeasureClient measureClient;
+    property public abstract androidx.health.services.client.PassiveMonitoringClient passiveMonitoringClient;
+  }
+
+  public final class HealthServicesException extends java.lang.Exception {
+    ctor public HealthServicesException(String message);
+  }
+
+  public final class ListenableFutureExtensionKt {
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend <T> Object? awaitWithException(com.google.common.util.concurrent.ListenableFuture<T>, kotlin.coroutines.Continuation<? super T>) throws androidx.health.services.client.HealthServicesException;
+  }
+
+  @kotlin.jvm.JvmDefaultWithCompatibility public interface MeasureCallback {
+    method public void onAvailabilityChanged(androidx.health.services.client.data.DeltaDataType<?,?> dataType, androidx.health.services.client.data.Availability availability);
+    method public void onDataReceived(androidx.health.services.client.data.DataPointContainer data);
+    method public default void onRegistered();
+    method public default void onRegistrationFailed(Throwable throwable);
+  }
+
+  public interface MeasureClient {
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> getCapabilitiesAsync();
+    method public void registerMeasureCallback(androidx.health.services.client.data.DeltaDataType<?,?> dataType, androidx.health.services.client.MeasureCallback callback);
+    method public void registerMeasureCallback(androidx.health.services.client.data.DeltaDataType<?,?> dataType, java.util.concurrent.Executor executor, androidx.health.services.client.MeasureCallback callback);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterMeasureCallbackAsync(androidx.health.services.client.data.DeltaDataType<?,?> dataType, androidx.health.services.client.MeasureCallback callback);
+  }
+
+  public final class MeasureClientExtensionKt {
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? getCapabilities(androidx.health.services.client.MeasureClient, kotlin.coroutines.Continuation<? super androidx.health.services.client.data.MeasureCapabilities>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? unregisterMeasureCallback(androidx.health.services.client.MeasureClient, androidx.health.services.client.data.DeltaDataType<?,?> dataType, androidx.health.services.client.MeasureCallback callback, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+  }
+
+  @kotlin.jvm.JvmDefaultWithCompatibility public interface PassiveListenerCallback {
+    method public default void onGoalCompleted(androidx.health.services.client.data.PassiveGoal goal);
+    method public default void onHealthEventReceived(androidx.health.services.client.data.HealthEvent event);
+    method public default void onNewDataPointsReceived(androidx.health.services.client.data.DataPointContainer dataPoints);
+    method public default void onPermissionLost();
+    method public default void onRegistered();
+    method public default void onRegistrationFailed(Throwable throwable);
+    method public default void onUserActivityInfoReceived(androidx.health.services.client.data.UserActivityInfo info);
+  }
+
+  public abstract class PassiveListenerService extends android.app.Service {
+    ctor public PassiveListenerService();
+    method public final android.os.IBinder? onBind(android.content.Intent intent);
+    method public void onGoalCompleted(androidx.health.services.client.data.PassiveGoal goal);
+    method public void onHealthEventReceived(androidx.health.services.client.data.HealthEvent event);
+    method public void onNewDataPointsReceived(androidx.health.services.client.data.DataPointContainer dataPoints);
+    method public void onPermissionLost();
+    method public void onUserActivityInfoReceived(androidx.health.services.client.data.UserActivityInfo info);
+  }
+
+  public interface PassiveMonitoringClient {
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearPassiveListenerCallbackAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearPassiveListenerServiceAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> flushAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> getCapabilitiesAsync();
+    method public void setPassiveListenerCallback(androidx.health.services.client.data.PassiveListenerConfig config, androidx.health.services.client.PassiveListenerCallback callback);
+    method public void setPassiveListenerCallback(androidx.health.services.client.data.PassiveListenerConfig config, java.util.concurrent.Executor executor, androidx.health.services.client.PassiveListenerCallback callback);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setPassiveListenerServiceAsync(Class<? extends androidx.health.services.client.PassiveListenerService> service, androidx.health.services.client.data.PassiveListenerConfig config);
+  }
+
+  public final class PassiveMonitoringClientExtensionKt {
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? clearPassiveListenerCallback(androidx.health.services.client.PassiveMonitoringClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? clearPassiveListenerService(androidx.health.services.client.PassiveMonitoringClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? flush(androidx.health.services.client.PassiveMonitoringClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? getCapabilities(androidx.health.services.client.PassiveMonitoringClient, kotlin.coroutines.Continuation<? super androidx.health.services.client.data.PassiveMonitoringCapabilities>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? setPassiveListenerService(androidx.health.services.client.PassiveMonitoringClient, Class<? extends androidx.health.services.client.PassiveListenerService> service, androidx.health.services.client.data.PassiveListenerConfig config, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+  }
+
+}
+
+package androidx.health.services.client.data {
+
+  public final class AggregateDataType<T extends java.lang.Number, D extends androidx.health.services.client.data.DataPoint<T>> extends androidx.health.services.client.data.DataType<T,D> {
+    ctor public AggregateDataType(String name, androidx.health.services.client.data.DataType.TimeType timeType, Class<T> valueClass);
+  }
+
+  @kotlin.jvm.JvmDefaultWithCompatibility public interface Availability {
+    method public int getId();
+    property public abstract int id;
+    field public static final androidx.health.services.client.data.Availability.Companion Companion;
+  }
+
+  public static final class Availability.Companion {
+  }
+
+  public final class BatchingMode {
+    field public static final androidx.health.services.client.data.BatchingMode.Companion Companion;
+    field public static final androidx.health.services.client.data.BatchingMode HEART_RATE_5_SECONDS;
+  }
+
+  public static final class BatchingMode.Companion {
+  }
+
+  public final class ComparisonType {
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.ComparisonType.Companion Companion;
+    field public static final androidx.health.services.client.data.ComparisonType GREATER_THAN;
+    field public static final androidx.health.services.client.data.ComparisonType GREATER_THAN_OR_EQUAL;
+    field public static final androidx.health.services.client.data.ComparisonType LESS_THAN;
+    field public static final androidx.health.services.client.data.ComparisonType LESS_THAN_OR_EQUAL;
+    field public static final androidx.health.services.client.data.ComparisonType UNKNOWN;
+  }
+
+  public static final class ComparisonType.Companion {
+  }
+
+  public final class CumulativeDataPoint<T extends java.lang.Number> extends androidx.health.services.client.data.DataPoint<T> {
+    ctor public CumulativeDataPoint(androidx.health.services.client.data.AggregateDataType<T,androidx.health.services.client.data.CumulativeDataPoint<T>> dataType, T total, java.time.Instant start, java.time.Instant end);
+    method public java.time.Instant getEnd();
+    method public java.time.Instant getStart();
+    method public T getTotal();
+    property public final java.time.Instant end;
+    property public final java.time.Instant start;
+    property public final T total;
+  }
+
+  public abstract class DataPoint<T> {
+    method public androidx.health.services.client.data.DataType<T,? extends androidx.health.services.client.data.DataPoint<T>> getDataType();
+    property public androidx.health.services.client.data.DataType<T,? extends androidx.health.services.client.data.DataPoint<T>> dataType;
+  }
+
+  public abstract class DataPointAccuracy {
+    ctor public DataPointAccuracy();
+  }
+
+  public final class DataPointContainer {
+    ctor public DataPointContainer(java.util.Map<androidx.health.services.client.data.DataType<?,?>,? extends java.util.List<? extends androidx.health.services.client.data.DataPoint<?>>> dataPoints);
+    ctor public DataPointContainer(java.util.List<? extends androidx.health.services.client.data.DataPoint<?>> dataPointList);
+    method public java.util.List<androidx.health.services.client.data.CumulativeDataPoint<?>> getCumulativeDataPoints();
+    method public <T, D extends androidx.health.services.client.data.DataPoint<T>> java.util.List<D> getData(androidx.health.services.client.data.DeltaDataType<T,D> type);
+    method public <T extends java.lang.Number, D extends androidx.health.services.client.data.DataPoint<T>> D? getData(androidx.health.services.client.data.AggregateDataType<T,D> type);
+    method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getDataTypes();
+    method public java.util.List<androidx.health.services.client.data.IntervalDataPoint<?>> getIntervalDataPoints();
+    method public java.util.List<androidx.health.services.client.data.SampleDataPoint<?>> getSampleDataPoints();
+    method public java.util.List<androidx.health.services.client.data.StatisticalDataPoint<?>> getStatisticalDataPoints();
+    property public final java.util.List<androidx.health.services.client.data.CumulativeDataPoint<?>> cumulativeDataPoints;
+    property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> dataTypes;
+    property public final java.util.List<androidx.health.services.client.data.IntervalDataPoint<?>> intervalDataPoints;
+    property public final java.util.List<androidx.health.services.client.data.SampleDataPoint<?>> sampleDataPoints;
+    property public final java.util.List<androidx.health.services.client.data.StatisticalDataPoint<?>> statisticalDataPoints;
+  }
+
+  public abstract class DataType<T, D extends androidx.health.services.client.data.DataPoint<T>> {
+    ctor public DataType(String name, androidx.health.services.client.data.DataType.TimeType timeType, Class<T> valueClass, boolean isAggregate);
+    method public final String getName();
+    method public final Class<T> getValueClass();
+    property public final String name;
+    property public final Class<T> valueClass;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> ABSOLUTE_ELEVATION;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> ABSOLUTE_ELEVATION_STATS;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> ACTIVE_EXERCISE_DURATION_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> CALORIES;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> CALORIES_DAILY;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> CALORIES_TOTAL;
+    field public static final androidx.health.services.client.data.DataType.Companion Companion;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> DECLINE_DISTANCE;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> DECLINE_DISTANCE_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> DECLINE_DURATION;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> DECLINE_DURATION_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> DISTANCE;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> DISTANCE_DAILY;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> DISTANCE_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> ELEVATION_GAIN;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> ELEVATION_GAIN_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> ELEVATION_LOSS;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> ELEVATION_LOSS_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> FLAT_GROUND_DISTANCE;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> FLAT_GROUND_DISTANCE_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> FLAT_GROUND_DURATION;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> FLAT_GROUND_DURATION_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> FLOORS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> FLOORS_DAILY;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> FLOORS_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> GOLF_SHOT_COUNT;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> GOLF_SHOT_COUNT_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> HEART_RATE_BPM;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> HEART_RATE_BPM_STATS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> INCLINE_DISTANCE;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> INCLINE_DISTANCE_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> INCLINE_DURATION;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> INCLINE_DURATION_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<androidx.health.services.client.data.LocationData,androidx.health.services.client.data.SampleDataPoint<androidx.health.services.client.data.LocationData>> LOCATION;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> PACE;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> PACE_STATS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> REP_COUNT;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> REP_COUNT_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> RESTING_EXERCISE_DURATION;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> RESTING_EXERCISE_DURATION_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> RUNNING_STEPS;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> RUNNING_STEPS_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> SPEED;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> SPEED_STATS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> STEPS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> STEPS_DAILY;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.SampleDataPoint<java.lang.Long>> STEPS_PER_MINUTE;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Long>> STEPS_PER_MINUTE_STATS;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> STEPS_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> SWIMMING_LAP_COUNT;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> SWIMMING_STROKES;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> SWIMMING_STROKES_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> VO2_MAX;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> VO2_MAX_STATS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> WALKING_STEPS;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> WALKING_STEPS_TOTAL;
+  }
+
+  public static final class DataType.Companion {
+  }
+
+  public static final class DataType.TimeType {
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.DataType.TimeType.Companion Companion;
+    field public static final androidx.health.services.client.data.DataType.TimeType INTERVAL;
+    field public static final androidx.health.services.client.data.DataType.TimeType SAMPLE;
+    field public static final androidx.health.services.client.data.DataType.TimeType UNKNOWN;
+  }
+
+  public static final class DataType.TimeType.Companion {
+  }
+
+  public final class DataTypeAvailability implements androidx.health.services.client.data.Availability {
+    method public static androidx.health.services.client.data.DataTypeAvailability? fromId(int id);
+    method public int getId();
+    method public String getName();
+    property public int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.DataTypeAvailability ACQUIRING;
+    field public static final androidx.health.services.client.data.DataTypeAvailability AVAILABLE;
+    field public static final androidx.health.services.client.data.DataTypeAvailability.Companion Companion;
+    field public static final androidx.health.services.client.data.DataTypeAvailability UNAVAILABLE;
+    field public static final androidx.health.services.client.data.DataTypeAvailability UNAVAILABLE_DEVICE_OFF_BODY;
+    field public static final androidx.health.services.client.data.DataTypeAvailability UNKNOWN;
+  }
+
+  public static final class DataTypeAvailability.Companion {
+    method public androidx.health.services.client.data.DataTypeAvailability? fromId(int id);
+  }
+
+  public final class DataTypeCondition<T extends java.lang.Number, D extends androidx.health.services.client.data.DataType<T, ? extends androidx.health.services.client.data.DataPoint<T>>> {
+    ctor public DataTypeCondition(D dataType, T threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+    method public androidx.health.services.client.data.ComparisonType getComparisonType();
+    method public D getDataType();
+    method public T getThreshold();
+    property public final androidx.health.services.client.data.ComparisonType comparisonType;
+    property public final D dataType;
+    property public final T threshold;
+  }
+
+  public final class DeltaDataType<T, D extends androidx.health.services.client.data.DataPoint<T>> extends androidx.health.services.client.data.DataType<T,D> {
+    ctor public DeltaDataType(String name, androidx.health.services.client.data.DataType.TimeType timeType, Class<T> valueClass);
+  }
+
+  public final class ExerciseCapabilities {
+    ctor public ExerciseCapabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> typeToCapabilities, optional java.util.Set<androidx.health.services.client.data.BatchingMode> supportedBatchingModeOverrides);
+    ctor public ExerciseCapabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> typeToCapabilities);
+    method public java.util.Set<androidx.health.services.client.data.ExerciseType> getAutoPauseAndResumeEnabledExercises();
+    method public androidx.health.services.client.data.ExerciseTypeCapabilities getExerciseTypeCapabilities(androidx.health.services.client.data.ExerciseType exercise);
+    method public java.util.Set<androidx.health.services.client.data.BatchingMode> getSupportedBatchingModeOverrides();
+    method public java.util.Set<androidx.health.services.client.data.ExerciseType> getSupportedExerciseTypes();
+    method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> getTypeToCapabilities();
+    property public final java.util.Set<androidx.health.services.client.data.ExerciseType> autoPauseAndResumeEnabledExercises;
+    property public final java.util.Set<androidx.health.services.client.data.BatchingMode> supportedBatchingModeOverrides;
+    property public final java.util.Set<androidx.health.services.client.data.ExerciseType> supportedExerciseTypes;
+    property public final java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> typeToCapabilities;
+  }
+
+  public final class ExerciseConfig {
+    ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters, optional androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig, optional java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides);
+    ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters, optional androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig);
+    ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters);
+    method public static androidx.health.services.client.data.ExerciseConfig.Builder builder(androidx.health.services.client.data.ExerciseType exerciseType);
+    method public java.util.Set<androidx.health.services.client.data.BatchingMode> getBatchingModeOverrides();
+    method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getDataTypes();
+    method public java.util.List<androidx.health.services.client.data.ExerciseGoal<?>> getExerciseGoals();
+    method public android.os.Bundle getExerciseParams();
+    method public androidx.health.services.client.data.ExerciseType getExerciseType();
+    method public androidx.health.services.client.data.ExerciseTypeConfig? getExerciseTypeConfig();
+    method public float getSwimmingPoolLengthMeters();
+    method public boolean isAutoPauseAndResumeEnabled();
+    method public boolean isGpsEnabled();
+    property public final java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides;
+    property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> dataTypes;
+    property public final java.util.List<androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals;
+    property public final android.os.Bundle exerciseParams;
+    property public final androidx.health.services.client.data.ExerciseType exerciseType;
+    property public final androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig;
+    property public final boolean isAutoPauseAndResumeEnabled;
+    property public final boolean isGpsEnabled;
+    property public final float swimmingPoolLengthMeters;
+    field public static final androidx.health.services.client.data.ExerciseConfig.Companion Companion;
+    field public static final float SWIMMING_POOL_LENGTH_UNSPECIFIED = 0.0f;
+  }
+
+  public static final class ExerciseConfig.Builder {
+    ctor public ExerciseConfig.Builder(androidx.health.services.client.data.ExerciseType exerciseType);
+    method public androidx.health.services.client.data.ExerciseConfig build();
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setBatchingModeOverrides(java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setDataTypes(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseGoals(java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseParams(android.os.Bundle exerciseParams);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseTypeConfig(androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setIsAutoPauseAndResumeEnabled(boolean isAutoPauseAndResumeEnabled);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setIsGpsEnabled(boolean isGpsEnabled);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setSwimmingPoolLengthMeters(float swimmingPoolLength);
+  }
+
+  public static final class ExerciseConfig.Companion {
+    method public androidx.health.services.client.data.ExerciseConfig.Builder builder(androidx.health.services.client.data.ExerciseType exerciseType);
+  }
+
+  public final class ExerciseGoal<T extends java.lang.Number> implements android.os.Parcelable {
+    method public static <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createMilestone(androidx.health.services.client.data.DataTypeCondition<T,androidx.health.services.client.data.AggregateDataType<T,?>> condition, T period);
+    method public static <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal<T> goal, T newThreshold);
+    method public static <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition<T,androidx.health.services.client.data.AggregateDataType<T,?>> condition);
+    method public int describeContents();
+    method public androidx.health.services.client.data.DataTypeCondition<T,androidx.health.services.client.data.AggregateDataType<T,?>> getDataTypeCondition();
+    method public androidx.health.services.client.data.ExerciseGoalType getExerciseGoalType();
+    method public T? getPeriod();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.DataTypeCondition<T,androidx.health.services.client.data.AggregateDataType<T,?>> dataTypeCondition;
+    property public final androidx.health.services.client.data.ExerciseGoalType exerciseGoalType;
+    property public final T? period;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseGoal<?>> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseGoal.Companion Companion;
+  }
+
+  public static final class ExerciseGoal.Companion {
+    method public <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createMilestone(androidx.health.services.client.data.DataTypeCondition<T,androidx.health.services.client.data.AggregateDataType<T,?>> condition, T period);
+    method public <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal<T> goal, T newThreshold);
+    method public <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition<T,androidx.health.services.client.data.AggregateDataType<T,?>> condition);
+  }
+
+  public final class ExerciseGoalType {
+    method public static androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.ExerciseGoalType.Companion Companion;
+    field public static final androidx.health.services.client.data.ExerciseGoalType MILESTONE;
+    field public static final androidx.health.services.client.data.ExerciseGoalType ONE_TIME_GOAL;
+  }
+
+  public static final class ExerciseGoalType.Companion {
+    method public androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+  }
+
+  public final class ExerciseInfo {
+    ctor public ExerciseInfo(int exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+    method public int getExerciseTrackedStatus();
+    method public androidx.health.services.client.data.ExerciseType getExerciseType();
+    property public final int exerciseTrackedStatus;
+    property public final androidx.health.services.client.data.ExerciseType exerciseType;
+  }
+
+  public final class ExerciseLapSummary {
+    ctor public ExerciseLapSummary(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.DataPointContainer lapMetrics);
+    method public java.time.Duration getActiveDuration();
+    method public java.time.Instant getEndTime();
+    method public int getLapCount();
+    method public androidx.health.services.client.data.DataPointContainer getLapMetrics();
+    method public java.time.Instant getStartTime();
+    property public final java.time.Duration activeDuration;
+    property public final java.time.Instant endTime;
+    property public final int lapCount;
+    property public final androidx.health.services.client.data.DataPointContainer lapMetrics;
+    property public final java.time.Instant startTime;
+  }
+
+  public final class ExerciseState {
+    method public static androidx.health.services.client.data.ExerciseState? fromId(int id);
+    method public int getId();
+    method public String getName();
+    method public boolean isEnded();
+    method public boolean isEnding();
+    method public boolean isPaused();
+    method public boolean isResuming();
+    property public final int id;
+    property public final boolean isEnded;
+    property public final boolean isEnding;
+    property public final boolean isPaused;
+    property public final boolean isResuming;
+    property public final String name;
+    field public static final androidx.health.services.client.data.ExerciseState ACTIVE;
+    field public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSED;
+    field public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSING;
+    field public static final androidx.health.services.client.data.ExerciseState AUTO_RESUMING;
+    field public static final androidx.health.services.client.data.ExerciseState.Companion Companion;
+    field public static final androidx.health.services.client.data.ExerciseState ENDED;
+    field public static final androidx.health.services.client.data.ExerciseState ENDING;
+    field public static final androidx.health.services.client.data.ExerciseState PREPARING;
+    field public static final androidx.health.services.client.data.ExerciseState USER_PAUSED;
+    field public static final androidx.health.services.client.data.ExerciseState USER_PAUSING;
+    field public static final androidx.health.services.client.data.ExerciseState USER_RESUMING;
+    field public static final androidx.health.services.client.data.ExerciseState USER_STARTING;
+  }
+
+  public static final class ExerciseState.Companion {
+    method public androidx.health.services.client.data.ExerciseState? fromId(int id);
+  }
+
+  public final class ExerciseStateInfo {
+    ctor public ExerciseStateInfo(androidx.health.services.client.data.ExerciseState exerciseState, int exerciseEndReason);
+    method public int getEndReason();
+    method public androidx.health.services.client.data.ExerciseState getState();
+    property public final int endReason;
+    property public final androidx.health.services.client.data.ExerciseState state;
+    field public static final androidx.health.services.client.data.ExerciseStateInfo.Companion Companion;
+  }
+
+  public static final class ExerciseStateInfo.Companion {
+  }
+
+  public final class ExerciseType {
+    method public static androidx.health.services.client.data.ExerciseType fromId(int id);
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.ExerciseType ALPINE_SKIING;
+    field public static final androidx.health.services.client.data.ExerciseType BACKPACKING;
+    field public static final androidx.health.services.client.data.ExerciseType BACK_EXTENSION;
+    field public static final androidx.health.services.client.data.ExerciseType BADMINTON;
+    field public static final androidx.health.services.client.data.ExerciseType BARBELL_SHOULDER_PRESS;
+    field public static final androidx.health.services.client.data.ExerciseType BASEBALL;
+    field public static final androidx.health.services.client.data.ExerciseType BASKETBALL;
+    field public static final androidx.health.services.client.data.ExerciseType BENCH_PRESS;
+    field public static final androidx.health.services.client.data.ExerciseType BIKING;
+    field public static final androidx.health.services.client.data.ExerciseType BIKING_STATIONARY;
+    field public static final androidx.health.services.client.data.ExerciseType BOOT_CAMP;
+    field public static final androidx.health.services.client.data.ExerciseType BOXING;
+    field public static final androidx.health.services.client.data.ExerciseType BURPEE;
+    field public static final androidx.health.services.client.data.ExerciseType CALISTHENICS;
+    field public static final androidx.health.services.client.data.ExerciseType CRICKET;
+    field public static final androidx.health.services.client.data.ExerciseType CROSS_COUNTRY_SKIING;
+    field public static final androidx.health.services.client.data.ExerciseType CRUNCH;
+    field public static final androidx.health.services.client.data.ExerciseType.Companion Companion;
+    field public static final androidx.health.services.client.data.ExerciseType DANCING;
+    field public static final androidx.health.services.client.data.ExerciseType DEADLIFT;
+    field public static final androidx.health.services.client.data.ExerciseType ELLIPTICAL;
+    field public static final androidx.health.services.client.data.ExerciseType EXERCISE_CLASS;
+    field public static final androidx.health.services.client.data.ExerciseType FENCING;
+    field public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AMERICAN;
+    field public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AUSTRALIAN;
+    field public static final androidx.health.services.client.data.ExerciseType FORWARD_TWIST;
+    field public static final androidx.health.services.client.data.ExerciseType FRISBEE_DISC;
+    field public static final androidx.health.services.client.data.ExerciseType GOLF;
+    field public static final androidx.health.services.client.data.ExerciseType GUIDED_BREATHING;
+    field public static final androidx.health.services.client.data.ExerciseType GYMNASTICS;
+    field public static final androidx.health.services.client.data.ExerciseType HANDBALL;
+    field public static final androidx.health.services.client.data.ExerciseType HIGH_INTENSITY_INTERVAL_TRAINING;
+    field public static final androidx.health.services.client.data.ExerciseType HIKING;
+    field public static final androidx.health.services.client.data.ExerciseType HORSE_RIDING;
+    field public static final androidx.health.services.client.data.ExerciseType ICE_HOCKEY;
+    field public static final androidx.health.services.client.data.ExerciseType ICE_SKATING;
+    field public static final androidx.health.services.client.data.ExerciseType INLINE_SKATING;
+    field public static final androidx.health.services.client.data.ExerciseType JUMPING_JACK;
+    field public static final androidx.health.services.client.data.ExerciseType JUMP_ROPE;
+    field public static final androidx.health.services.client.data.ExerciseType LAT_PULL_DOWN;
+    field public static final androidx.health.services.client.data.ExerciseType LUNGE;
+    field public static final androidx.health.services.client.data.ExerciseType MARTIAL_ARTS;
+    field public static final androidx.health.services.client.data.ExerciseType MEDITATION;
+    field public static final androidx.health.services.client.data.ExerciseType MOUNTAIN_BIKING;
+    field public static final androidx.health.services.client.data.ExerciseType ORIENTEERING;
+    field public static final androidx.health.services.client.data.ExerciseType PADDLING;
+    field public static final androidx.health.services.client.data.ExerciseType PARA_GLIDING;
+    field public static final androidx.health.services.client.data.ExerciseType PILATES;
+    field public static final androidx.health.services.client.data.ExerciseType PLANK;
+    field public static final androidx.health.services.client.data.ExerciseType RACQUETBALL;
+    field public static final androidx.health.services.client.data.ExerciseType ROCK_CLIMBING;
+    field public static final androidx.health.services.client.data.ExerciseType ROLLER_HOCKEY;
+    field public static final androidx.health.services.client.data.ExerciseType ROLLER_SKATING;
+    field public static final androidx.health.services.client.data.ExerciseType ROWING;
+    field public static final androidx.health.services.client.data.ExerciseType ROWING_MACHINE;
+    field public static final androidx.health.services.client.data.ExerciseType RUGBY;
+    field public static final androidx.health.services.client.data.ExerciseType RUNNING;
+    field public static final androidx.health.services.client.data.ExerciseType RUNNING_TREADMILL;
+    field public static final androidx.health.services.client.data.ExerciseType SAILING;
+    field public static final androidx.health.services.client.data.ExerciseType SCUBA_DIVING;
+    field public static final androidx.health.services.client.data.ExerciseType SKATING;
+    field public static final androidx.health.services.client.data.ExerciseType SKIING;
+    field public static final androidx.health.services.client.data.ExerciseType SNOWBOARDING;
+    field public static final androidx.health.services.client.data.ExerciseType SNOWSHOEING;
+    field public static final androidx.health.services.client.data.ExerciseType SOCCER;
+    field public static final androidx.health.services.client.data.ExerciseType SOFTBALL;
+    field public static final androidx.health.services.client.data.ExerciseType SQUASH;
+    field public static final androidx.health.services.client.data.ExerciseType SQUAT;
+    field public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING;
+    field public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING_MACHINE;
+    field public static final androidx.health.services.client.data.ExerciseType STRENGTH_TRAINING;
+    field public static final androidx.health.services.client.data.ExerciseType STRETCHING;
+    field public static final androidx.health.services.client.data.ExerciseType SURFING;
+    field public static final androidx.health.services.client.data.ExerciseType SWIMMING_OPEN_WATER;
+    field public static final androidx.health.services.client.data.ExerciseType SWIMMING_POOL;
+    field public static final androidx.health.services.client.data.ExerciseType TABLE_TENNIS;
+    field public static final androidx.health.services.client.data.ExerciseType TENNIS;
+    field public static final androidx.health.services.client.data.ExerciseType UNKNOWN;
+    field public static final androidx.health.services.client.data.ExerciseType UPPER_TWIST;
+    field public static final androidx.health.services.client.data.ExerciseType VOLLEYBALL;
+    field public static final androidx.health.services.client.data.ExerciseType WALKING;
+    field public static final androidx.health.services.client.data.ExerciseType WATER_POLO;
+    field public static final androidx.health.services.client.data.ExerciseType WEIGHTLIFTING;
+    field public static final androidx.health.services.client.data.ExerciseType WORKOUT;
+    field public static final androidx.health.services.client.data.ExerciseType YACHTING;
+    field public static final androidx.health.services.client.data.ExerciseType YOGA;
+  }
+
+  public static final class ExerciseType.Companion {
+    method public androidx.health.services.client.data.ExerciseType fromId(int id);
+  }
+
+  public final class ExerciseTypeCapabilities {
+    ctor public ExerciseTypeCapabilities(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> supportedDataTypes, java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,? extends java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,? extends java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume);
+    method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getSupportedDataTypes();
+    method public java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedGoals();
+    method public java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedMilestones();
+    method public boolean getSupportsAutoPauseAndResume();
+    property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> supportedDataTypes;
+    property public final java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals;
+    property public final java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones;
+    property public final boolean supportsAutoPauseAndResume;
+  }
+
+  public abstract class ExerciseTypeConfig {
+    field public static final androidx.health.services.client.data.ExerciseTypeConfig.Companion Companion;
+  }
+
+  public static final class ExerciseTypeConfig.Companion {
+  }
+
+  public final class ExerciseUpdate {
+    method public java.time.Duration getActiveDurationAtDataPoint(androidx.health.services.client.data.IntervalDataPoint<?> dataPoint);
+    method public java.time.Duration getActiveDurationAtDataPoint(androidx.health.services.client.data.SampleDataPoint<?> dataPoint);
+    method public androidx.health.services.client.data.ExerciseUpdate.ActiveDurationCheckpoint? getActiveDurationCheckpoint();
+    method public androidx.health.services.client.data.ExerciseConfig? getExerciseConfig();
+    method public androidx.health.services.client.data.ExerciseStateInfo getExerciseStateInfo();
+    method public java.util.Set<androidx.health.services.client.data.ExerciseGoal<? extends java.lang.Number>> getLatestAchievedGoals();
+    method public androidx.health.services.client.data.DataPointContainer getLatestMetrics();
+    method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> getLatestMilestoneMarkerSummaries();
+    method public java.time.Instant? getStartTime();
+    method public java.time.Duration getUpdateDurationFromBoot();
+    property public final androidx.health.services.client.data.ExerciseUpdate.ActiveDurationCheckpoint? activeDurationCheckpoint;
+    property public final androidx.health.services.client.data.ExerciseConfig? exerciseConfig;
+    property public final androidx.health.services.client.data.ExerciseStateInfo exerciseStateInfo;
+    property public final java.util.Set<androidx.health.services.client.data.ExerciseGoal<? extends java.lang.Number>> latestAchievedGoals;
+    property public final androidx.health.services.client.data.DataPointContainer latestMetrics;
+    property public final java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries;
+    property public final java.time.Instant? startTime;
+    field public static final androidx.health.services.client.data.ExerciseUpdate.Companion Companion;
+  }
+
+  public static final class ExerciseUpdate.ActiveDurationCheckpoint {
+    ctor public ExerciseUpdate.ActiveDurationCheckpoint(java.time.Instant time, java.time.Duration activeDuration);
+    method public java.time.Duration getActiveDuration();
+    method public java.time.Instant getTime();
+    property public final java.time.Duration activeDuration;
+    property public final java.time.Instant time;
+  }
+
+  public static final class ExerciseUpdate.Companion {
+  }
+
+  public final class GolfExerciseTypeConfig extends androidx.health.services.client.data.ExerciseTypeConfig {
+    ctor public GolfExerciseTypeConfig(optional androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo golfShotTrackingPlaceInfo);
+    method public androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo getGolfShotTrackingPlaceInfo();
+    property public final androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo golfShotTrackingPlaceInfo;
+  }
+
+  public static final class GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo {
+    method public int getPlaceInfoId();
+    property public final int placeInfoId;
+    field public static final androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo.Companion Companion;
+    field public static final androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo GOLF_SHOT_TRACKING_PLACE_INFO_FAIRWAY;
+    field public static final androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo GOLF_SHOT_TRACKING_PLACE_INFO_PUTTING_GREEN;
+    field public static final androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo GOLF_SHOT_TRACKING_PLACE_INFO_TEE_BOX;
+    field public static final androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo GOLF_SHOT_TRACKING_PLACE_INFO_UNSPECIFIED;
+  }
+
+  public static final class GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo.Companion {
+  }
+
+  public final class HealthEvent {
+    ctor public HealthEvent(androidx.health.services.client.data.HealthEvent.Type type, java.time.Instant eventTime, androidx.health.services.client.data.DataPointContainer metrics);
+    method public java.time.Instant getEventTime();
+    method public androidx.health.services.client.data.DataPointContainer getMetrics();
+    method public androidx.health.services.client.data.HealthEvent.Type getType();
+    property public final java.time.Instant eventTime;
+    property public final androidx.health.services.client.data.DataPointContainer metrics;
+    property public final androidx.health.services.client.data.HealthEvent.Type type;
+  }
+
+  public static final class HealthEvent.Type {
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.HealthEvent.Type.Companion Companion;
+    field public static final androidx.health.services.client.data.HealthEvent.Type FALL_DETECTED;
+    field public static final androidx.health.services.client.data.HealthEvent.Type UNKNOWN;
+  }
+
+  public static final class HealthEvent.Type.Companion {
+  }
+
+  public final class HeartRateAccuracy extends androidx.health.services.client.data.DataPointAccuracy {
+    ctor public HeartRateAccuracy(androidx.health.services.client.data.HeartRateAccuracy.SensorStatus sensorStatus);
+    method public androidx.health.services.client.data.HeartRateAccuracy.SensorStatus getSensorStatus();
+    property public final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus sensorStatus;
+  }
+
+  public static final class HeartRateAccuracy.SensorStatus {
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus ACCURACY_HIGH;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus ACCURACY_LOW;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus ACCURACY_MEDIUM;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus.Companion Companion;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus NO_CONTACT;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus UNKNOWN;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus UNRELIABLE;
+  }
+
+  public static final class HeartRateAccuracy.SensorStatus.Companion {
+  }
+
+  public final class IntervalDataPoint<T> extends androidx.health.services.client.data.DataPoint<T> {
+    ctor public IntervalDataPoint(androidx.health.services.client.data.DataType<T,? extends androidx.health.services.client.data.IntervalDataPoint<T>> dataType, T value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata, optional androidx.health.services.client.data.DataPointAccuracy? accuracy);
+    method public androidx.health.services.client.data.DataPointAccuracy? getAccuracy();
+    method public androidx.health.services.client.data.DataType<T,? extends androidx.health.services.client.data.IntervalDataPoint<T>> getDataType();
+    method public java.time.Duration getEndDurationFromBoot();
+    method public java.time.Instant getEndInstant(java.time.Instant bootInstant);
+    method public android.os.Bundle getMetadata();
+    method public java.time.Duration getStartDurationFromBoot();
+    method public java.time.Instant getStartInstant(java.time.Instant bootInstant);
+    method public T getValue();
+    property public final androidx.health.services.client.data.DataPointAccuracy? accuracy;
+    property public androidx.health.services.client.data.DataType<T,? extends androidx.health.services.client.data.IntervalDataPoint<T>> dataType;
+    property public final java.time.Duration endDurationFromBoot;
+    property public final android.os.Bundle metadata;
+    property public final java.time.Duration startDurationFromBoot;
+    property public final T value;
+  }
+
+  public final class LocationAccuracy extends androidx.health.services.client.data.DataPointAccuracy {
+    ctor public LocationAccuracy(@FloatRange(from=0.0) double horizontalPositionErrorMeters, optional @FloatRange(from=0.0) double verticalPositionErrorMeters);
+    method public double getHorizontalPositionErrorMeters();
+    method public double getVerticalPositionErrorMeters();
+    property public final double horizontalPositionErrorMeters;
+    property public final double verticalPositionErrorMeters;
+    field public static final androidx.health.services.client.data.LocationAccuracy.Companion Companion;
+  }
+
+  public static final class LocationAccuracy.Companion {
+  }
+
+  public final class LocationAvailability implements androidx.health.services.client.data.Availability {
+    method public static androidx.health.services.client.data.LocationAvailability? fromId(int id);
+    method public int getId();
+    method public String getName();
+    property public int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.LocationAvailability ACQUIRED_TETHERED;
+    field public static final androidx.health.services.client.data.LocationAvailability ACQUIRED_UNTETHERED;
+    field public static final androidx.health.services.client.data.LocationAvailability ACQUIRING;
+    field public static final androidx.health.services.client.data.LocationAvailability.Companion Companion;
+    field public static final androidx.health.services.client.data.LocationAvailability NO_GNSS;
+    field public static final androidx.health.services.client.data.LocationAvailability UNAVAILABLE;
+    field public static final androidx.health.services.client.data.LocationAvailability UNKNOWN;
+  }
+
+  public static final class LocationAvailability.Companion {
+    method public androidx.health.services.client.data.LocationAvailability? fromId(int id);
+  }
+
+  public final class LocationData {
+    ctor public LocationData(@FloatRange(from=-90.0, to=90.0) double latitude, @FloatRange(from=-180.0, to=180.0) double longitude, optional double altitude, optional double bearing);
+    method public double getAltitude();
+    method public double getBearing();
+    method public double getLatitude();
+    method public double getLongitude();
+    property public final double altitude;
+    property public final double bearing;
+    property public final double latitude;
+    property public final double longitude;
+    field public static final double ALTITUDE_UNAVAILABLE = (0.0/0.0);
+    field public static final double BEARING_UNAVAILABLE = (0.0/0.0);
+  }
+
+  public final class MeasureCapabilities {
+    ctor public MeasureCapabilities(java.util.Set<? extends androidx.health.services.client.data.DeltaDataType<?,?>> supportedDataTypesMeasure);
+    method public java.util.Set<androidx.health.services.client.data.DeltaDataType<?,?>> getSupportedDataTypesMeasure();
+    property public final java.util.Set<androidx.health.services.client.data.DeltaDataType<?,?>> supportedDataTypesMeasure;
+  }
+
+  public final class MilestoneMarkerSummary {
+    ctor public MilestoneMarkerSummary(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.ExerciseGoal<? extends java.lang.Number> achievedGoal, androidx.health.services.client.data.DataPointContainer summaryMetrics);
+    method public androidx.health.services.client.data.ExerciseGoal<? extends java.lang.Number> getAchievedGoal();
+    method public java.time.Duration getActiveDuration();
+    method public java.time.Instant getEndTime();
+    method public java.time.Instant getStartTime();
+    method public androidx.health.services.client.data.DataPointContainer getSummaryMetrics();
+    property public final androidx.health.services.client.data.ExerciseGoal<? extends java.lang.Number> achievedGoal;
+    property public final java.time.Duration activeDuration;
+    property public final java.time.Instant endTime;
+    property public final java.time.Instant startTime;
+    property public final androidx.health.services.client.data.DataPointContainer summaryMetrics;
+  }
+
+  public final class PassiveGoal {
+    ctor public PassiveGoal(androidx.health.services.client.data.DataTypeCondition<? extends java.lang.Number,? extends androidx.health.services.client.data.DeltaDataType<? extends java.lang.Number,?>> dataTypeCondition);
+    method public androidx.health.services.client.data.DataTypeCondition<? extends java.lang.Number,? extends androidx.health.services.client.data.DeltaDataType<? extends java.lang.Number,?>> getDataTypeCondition();
+    property public final androidx.health.services.client.data.DataTypeCondition<? extends java.lang.Number,? extends androidx.health.services.client.data.DeltaDataType<? extends java.lang.Number,?>> dataTypeCondition;
+  }
+
+  public final class PassiveListenerConfig {
+    ctor public PassiveListenerConfig(java.util.Set<? extends androidx.health.services.client.data.DataType<? extends java.lang.Object,? extends androidx.health.services.client.data.DataPoint<?>>> dataTypes, boolean shouldUserActivityInfoBeRequested, java.util.Set<androidx.health.services.client.data.PassiveGoal> dailyGoals, java.util.Set<androidx.health.services.client.data.HealthEvent.Type> healthEventTypes);
+    method public static androidx.health.services.client.data.PassiveListenerConfig.Builder builder();
+    method public java.util.Set<androidx.health.services.client.data.PassiveGoal> getDailyGoals();
+    method public java.util.Set<androidx.health.services.client.data.DataType<? extends java.lang.Object,? extends androidx.health.services.client.data.DataPoint<?>>> getDataTypes();
+    method public java.util.Set<androidx.health.services.client.data.HealthEvent.Type> getHealthEventTypes();
+    method public boolean getShouldUserActivityInfoBeRequested();
+    property public final java.util.Set<androidx.health.services.client.data.PassiveGoal> dailyGoals;
+    property public final java.util.Set<androidx.health.services.client.data.DataType<? extends java.lang.Object,? extends androidx.health.services.client.data.DataPoint<?>>> dataTypes;
+    property public final java.util.Set<androidx.health.services.client.data.HealthEvent.Type> healthEventTypes;
+    property public final boolean shouldUserActivityInfoBeRequested;
+    field public static final androidx.health.services.client.data.PassiveListenerConfig.Companion Companion;
+  }
+
+  public static final class PassiveListenerConfig.Builder {
+    ctor public PassiveListenerConfig.Builder();
+    method public androidx.health.services.client.data.PassiveListenerConfig build();
+    method public androidx.health.services.client.data.PassiveListenerConfig.Builder setDailyGoals(java.util.Set<androidx.health.services.client.data.PassiveGoal> dailyGoals);
+    method public androidx.health.services.client.data.PassiveListenerConfig.Builder setDataTypes(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes);
+    method public androidx.health.services.client.data.PassiveListenerConfig.Builder setHealthEventTypes(java.util.Set<androidx.health.services.client.data.HealthEvent.Type> healthEventTypes);
+    method public androidx.health.services.client.data.PassiveListenerConfig.Builder setShouldUserActivityInfoBeRequested(boolean shouldUserActivityInfoBeRequested);
+  }
+
+  public static final class PassiveListenerConfig.Companion {
+    method public androidx.health.services.client.data.PassiveListenerConfig.Builder builder();
+  }
+
+  public final class PassiveMonitoringCapabilities {
+    ctor public PassiveMonitoringCapabilities(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> supportedDataTypesPassiveMonitoring, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> supportedDataTypesPassiveGoals, java.util.Set<androidx.health.services.client.data.HealthEvent.Type> supportedHealthEventTypes, java.util.Set<androidx.health.services.client.data.UserActivityState> supportedUserActivityStates);
+    method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getSupportedDataTypesPassiveGoals();
+    method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getSupportedDataTypesPassiveMonitoring();
+    method public java.util.Set<androidx.health.services.client.data.HealthEvent.Type> getSupportedHealthEventTypes();
+    method public java.util.Set<androidx.health.services.client.data.UserActivityState> getSupportedUserActivityStates();
+    property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> supportedDataTypesPassiveGoals;
+    property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> supportedDataTypesPassiveMonitoring;
+    property public final java.util.Set<androidx.health.services.client.data.HealthEvent.Type> supportedHealthEventTypes;
+    property public final java.util.Set<androidx.health.services.client.data.UserActivityState> supportedUserActivityStates;
+  }
+
+  public final class PassiveMonitoringUpdate {
+    ctor public PassiveMonitoringUpdate(androidx.health.services.client.data.DataPointContainer dataPoints, java.util.List<androidx.health.services.client.data.UserActivityInfo> userActivityInfoUpdates);
+    method public androidx.health.services.client.data.DataPointContainer getDataPoints();
+    method public java.util.List<androidx.health.services.client.data.UserActivityInfo> getUserActivityInfoUpdates();
+    property public final androidx.health.services.client.data.DataPointContainer dataPoints;
+    property public final java.util.List<androidx.health.services.client.data.UserActivityInfo> userActivityInfoUpdates;
+  }
+
+  public final class SampleDataPoint<T> extends androidx.health.services.client.data.DataPoint<T> {
+    ctor public SampleDataPoint(androidx.health.services.client.data.DataType<T,androidx.health.services.client.data.SampleDataPoint<T>> dataType, T value, java.time.Duration timeDurationFromBoot, optional android.os.Bundle metadata, optional androidx.health.services.client.data.DataPointAccuracy? accuracy);
+    method public androidx.health.services.client.data.DataPointAccuracy? getAccuracy();
+    method public androidx.health.services.client.data.DataType<T,androidx.health.services.client.data.SampleDataPoint<T>> getDataType();
+    method public android.os.Bundle getMetadata();
+    method public java.time.Duration getTimeDurationFromBoot();
+    method public java.time.Instant getTimeInstant(java.time.Instant bootInstant);
+    method public T getValue();
+    property public final androidx.health.services.client.data.DataPointAccuracy? accuracy;
+    property public androidx.health.services.client.data.DataType<T,androidx.health.services.client.data.SampleDataPoint<T>> dataType;
+    property public final android.os.Bundle metadata;
+    property public final java.time.Duration timeDurationFromBoot;
+    property public final T value;
+  }
+
+  public final class StatisticalDataPoint<T extends java.lang.Number> extends androidx.health.services.client.data.DataPoint<T> {
+    ctor public StatisticalDataPoint(androidx.health.services.client.data.AggregateDataType<T,androidx.health.services.client.data.StatisticalDataPoint<T>> dataType, T min, T max, T average, java.time.Instant start, java.time.Instant end);
+    method public T getAverage();
+    method public java.time.Instant getEnd();
+    method public T getMax();
+    method public T getMin();
+    method public java.time.Instant getStart();
+    property public final T average;
+    property public final java.time.Instant end;
+    property public final T max;
+    property public final T min;
+    property public final java.time.Instant start;
+    field public static final androidx.health.services.client.data.StatisticalDataPoint.Companion Companion;
+  }
+
+  public static final class StatisticalDataPoint.Companion {
+  }
+
+  public final class UserActivityInfo {
+    ctor public UserActivityInfo(androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseInfo? exerciseInfo, java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.UserActivityInfo createActiveExerciseState(androidx.health.services.client.data.ExerciseInfo exerciseInfo, java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.UserActivityInfo createAsleepState(java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.UserActivityInfo createPassiveActivityState(java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.UserActivityInfo createUnknownTypeState(java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.ExerciseInfo? getExerciseInfo();
+    method public java.time.Instant getStateChangeTime();
+    method public androidx.health.services.client.data.UserActivityState getUserActivityState();
+    property public final androidx.health.services.client.data.ExerciseInfo? exerciseInfo;
+    property public final java.time.Instant stateChangeTime;
+    property public final androidx.health.services.client.data.UserActivityState userActivityState;
+    field public static final androidx.health.services.client.data.UserActivityInfo.Companion Companion;
+  }
+
+  public static final class UserActivityInfo.Companion {
+    method public androidx.health.services.client.data.UserActivityInfo createActiveExerciseState(androidx.health.services.client.data.ExerciseInfo exerciseInfo, java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.UserActivityInfo createAsleepState(java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.UserActivityInfo createPassiveActivityState(java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.UserActivityInfo createUnknownTypeState(java.time.Instant stateChangeTime);
+  }
+
+  public final class UserActivityState {
+    ctor public UserActivityState(int id, String name);
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.UserActivityState.Companion Companion;
+    field public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_ASLEEP;
+    field public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_EXERCISE;
+    field public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_PASSIVE;
+    field public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_UNKNOWN;
+  }
+
+  public static final class UserActivityState.Companion {
+  }
+
+  public final class WarmUpConfig {
+    ctor public WarmUpConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DeltaDataType<?,?>> dataTypes);
+    method public java.util.Set<androidx.health.services.client.data.DeltaDataType<?,?>> getDataTypes();
+    method public androidx.health.services.client.data.ExerciseType getExerciseType();
+    property public final java.util.Set<androidx.health.services.client.data.DeltaDataType<?,?>> dataTypes;
+    property public final androidx.health.services.client.data.ExerciseType exerciseType;
+  }
+
+}
+
diff --git a/health/health-services-client/api/current.ignore b/health/health-services-client/api/current.ignore
index a882151..091f290 100644
--- a/health/health-services-client/api/current.ignore
+++ b/health/health-services-client/api/current.ignore
@@ -1,3 +1,3 @@
 // Baseline format: 1.0
-AddedAbstractMethod: androidx.health.services.client.ExerciseClient#updateExerciseTypeConfigAsync(androidx.health.services.client.data.ExerciseTypeConfig):
-    Added method androidx.health.services.client.ExerciseClient.updateExerciseTypeConfigAsync(androidx.health.services.client.data.ExerciseTypeConfig)
+AddedAbstractMethod: androidx.health.services.client.ExerciseClient#overrideBatchingModesForActiveExerciseAsync(java.util.Set<androidx.health.services.client.data.BatchingMode>):
+    Added method androidx.health.services.client.ExerciseClient.overrideBatchingModesForActiveExerciseAsync(java.util.Set<androidx.health.services.client.data.BatchingMode>)
diff --git a/health/health-services-client/api/current.txt b/health/health-services-client/api/current.txt
index b985692..9d0902d 100644
--- a/health/health-services-client/api/current.txt
+++ b/health/health-services-client/api/current.txt
@@ -10,6 +10,7 @@
     method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfoAsync();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLapAsync();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideAutoPauseAndResumeForActiveExerciseAsync(boolean enabled);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideBatchingModesForActiveExerciseAsync(java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModes);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> pauseExerciseAsync();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> prepareExerciseAsync(androidx.health.services.client.data.WarmUpConfig configuration);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> removeGoalFromActiveExerciseAsync(androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal);
@@ -141,6 +142,14 @@
   public static final class Availability.Companion {
   }
 
+  public final class BatchingMode {
+    field public static final androidx.health.services.client.data.BatchingMode.Companion Companion;
+    field public static final androidx.health.services.client.data.BatchingMode HEART_RATE_5_SECONDS;
+  }
+
+  public static final class BatchingMode.Companion {
+  }
+
   public final class ComparisonType {
     method public int getId();
     method public String getName();
@@ -307,20 +316,25 @@
   }
 
   public final class ExerciseCapabilities {
+    ctor public ExerciseCapabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> typeToCapabilities, optional java.util.Set<androidx.health.services.client.data.BatchingMode> supportedBatchingModeOverrides);
     ctor public ExerciseCapabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> typeToCapabilities);
     method public java.util.Set<androidx.health.services.client.data.ExerciseType> getAutoPauseAndResumeEnabledExercises();
     method public androidx.health.services.client.data.ExerciseTypeCapabilities getExerciseTypeCapabilities(androidx.health.services.client.data.ExerciseType exercise);
+    method public java.util.Set<androidx.health.services.client.data.BatchingMode> getSupportedBatchingModeOverrides();
     method public java.util.Set<androidx.health.services.client.data.ExerciseType> getSupportedExerciseTypes();
     method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> getTypeToCapabilities();
     property public final java.util.Set<androidx.health.services.client.data.ExerciseType> autoPauseAndResumeEnabledExercises;
+    property public final java.util.Set<androidx.health.services.client.data.BatchingMode> supportedBatchingModeOverrides;
     property public final java.util.Set<androidx.health.services.client.data.ExerciseType> supportedExerciseTypes;
     property public final java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> typeToCapabilities;
   }
 
   public final class ExerciseConfig {
+    ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters, optional androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig, optional java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides);
     ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters, optional androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig);
     ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters);
     method public static androidx.health.services.client.data.ExerciseConfig.Builder builder(androidx.health.services.client.data.ExerciseType exerciseType);
+    method public java.util.Set<androidx.health.services.client.data.BatchingMode> getBatchingModeOverrides();
     method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getDataTypes();
     method public java.util.List<androidx.health.services.client.data.ExerciseGoal<?>> getExerciseGoals();
     method public android.os.Bundle getExerciseParams();
@@ -329,6 +343,7 @@
     method public float getSwimmingPoolLengthMeters();
     method public boolean isAutoPauseAndResumeEnabled();
     method public boolean isGpsEnabled();
+    property public final java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides;
     property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> dataTypes;
     property public final java.util.List<androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals;
     property public final android.os.Bundle exerciseParams;
@@ -344,6 +359,7 @@
   public static final class ExerciseConfig.Builder {
     ctor public ExerciseConfig.Builder(androidx.health.services.client.data.ExerciseType exerciseType);
     method public androidx.health.services.client.data.ExerciseConfig build();
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setBatchingModeOverrides(java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides);
     method public androidx.health.services.client.data.ExerciseConfig.Builder setDataTypes(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes);
     method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseGoals(java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals);
     method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseParams(android.os.Bundle exerciseParams);
diff --git a/health/health-services-client/api/public_plus_experimental_1.0.0-beta03.txt b/health/health-services-client/api/public_plus_experimental_1.0.0-beta03.txt
new file mode 100644
index 0000000..9d0902d
--- /dev/null
+++ b/health/health-services-client/api/public_plus_experimental_1.0.0-beta03.txt
@@ -0,0 +1,909 @@
+// Signature format: 4.0
+package androidx.health.services.client {
+
+  @kotlin.jvm.JvmDefaultWithCompatibility public interface ExerciseClient {
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> addGoalToActiveExerciseAsync(androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearUpdateCallbackAsync(androidx.health.services.client.ExerciseUpdateCallback callback);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> endExerciseAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> flushAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseCapabilities> getCapabilitiesAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfoAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLapAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideAutoPauseAndResumeForActiveExerciseAsync(boolean enabled);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideBatchingModesForActiveExerciseAsync(java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModes);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> pauseExerciseAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> prepareExerciseAsync(androidx.health.services.client.data.WarmUpConfig configuration);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> removeGoalFromActiveExerciseAsync(androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> resumeExerciseAsync();
+    method public void setUpdateCallback(androidx.health.services.client.ExerciseUpdateCallback callback);
+    method public void setUpdateCallback(java.util.concurrent.Executor executor, androidx.health.services.client.ExerciseUpdateCallback callback);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> startExerciseAsync(androidx.health.services.client.data.ExerciseConfig configuration);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> updateExerciseTypeConfigAsync(androidx.health.services.client.data.ExerciseTypeConfig exerciseTypeConfig);
+  }
+
+  public final class ExerciseClientExtensionKt {
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? addGoalToActiveExercise(androidx.health.services.client.ExerciseClient, androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? clearUpdateCallback(androidx.health.services.client.ExerciseClient, androidx.health.services.client.ExerciseUpdateCallback callback, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? endExercise(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? flush(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? getCapabilities(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super androidx.health.services.client.data.ExerciseCapabilities>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? getCurrentExerciseInfo(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super androidx.health.services.client.data.ExerciseInfo>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? markLap(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? overrideAutoPauseAndResumeForActiveExercise(androidx.health.services.client.ExerciseClient, boolean enabled, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? pauseExercise(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? prepareExercise(androidx.health.services.client.ExerciseClient, androidx.health.services.client.data.WarmUpConfig configuration, kotlin.coroutines.Continuation<? super kotlin.Unit>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? removeGoalFromActiveExercise(androidx.health.services.client.ExerciseClient, androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? resumeExercise(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? startExercise(androidx.health.services.client.ExerciseClient, androidx.health.services.client.data.ExerciseConfig configuration, kotlin.coroutines.Continuation<? super kotlin.Unit>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? updateExerciseTypeConfig(androidx.health.services.client.ExerciseClient, androidx.health.services.client.data.ExerciseTypeConfig exerciseTypeConfig, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+  }
+
+  public interface ExerciseUpdateCallback {
+    method public void onAvailabilityChanged(androidx.health.services.client.data.DataType<?,?> dataType, androidx.health.services.client.data.Availability availability);
+    method public void onExerciseUpdateReceived(androidx.health.services.client.data.ExerciseUpdate update);
+    method public void onLapSummaryReceived(androidx.health.services.client.data.ExerciseLapSummary lapSummary);
+    method public void onRegistered();
+    method public void onRegistrationFailed(Throwable throwable);
+  }
+
+  public final class HealthServices {
+    method public static androidx.health.services.client.HealthServicesClient getClient(android.content.Context context);
+    field public static final androidx.health.services.client.HealthServices INSTANCE;
+  }
+
+  public interface HealthServicesClient {
+    method public androidx.health.services.client.ExerciseClient getExerciseClient();
+    method public androidx.health.services.client.MeasureClient getMeasureClient();
+    method public androidx.health.services.client.PassiveMonitoringClient getPassiveMonitoringClient();
+    property public abstract androidx.health.services.client.ExerciseClient exerciseClient;
+    property public abstract androidx.health.services.client.MeasureClient measureClient;
+    property public abstract androidx.health.services.client.PassiveMonitoringClient passiveMonitoringClient;
+  }
+
+  public final class HealthServicesException extends java.lang.Exception {
+    ctor public HealthServicesException(String message);
+  }
+
+  public final class ListenableFutureExtensionKt {
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend <T> Object? awaitWithException(com.google.common.util.concurrent.ListenableFuture<T>, kotlin.coroutines.Continuation<? super T>) throws androidx.health.services.client.HealthServicesException;
+  }
+
+  @kotlin.jvm.JvmDefaultWithCompatibility public interface MeasureCallback {
+    method public void onAvailabilityChanged(androidx.health.services.client.data.DeltaDataType<?,?> dataType, androidx.health.services.client.data.Availability availability);
+    method public void onDataReceived(androidx.health.services.client.data.DataPointContainer data);
+    method public default void onRegistered();
+    method public default void onRegistrationFailed(Throwable throwable);
+  }
+
+  public interface MeasureClient {
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> getCapabilitiesAsync();
+    method public void registerMeasureCallback(androidx.health.services.client.data.DeltaDataType<?,?> dataType, androidx.health.services.client.MeasureCallback callback);
+    method public void registerMeasureCallback(androidx.health.services.client.data.DeltaDataType<?,?> dataType, java.util.concurrent.Executor executor, androidx.health.services.client.MeasureCallback callback);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterMeasureCallbackAsync(androidx.health.services.client.data.DeltaDataType<?,?> dataType, androidx.health.services.client.MeasureCallback callback);
+  }
+
+  public final class MeasureClientExtensionKt {
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? getCapabilities(androidx.health.services.client.MeasureClient, kotlin.coroutines.Continuation<? super androidx.health.services.client.data.MeasureCapabilities>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? unregisterMeasureCallback(androidx.health.services.client.MeasureClient, androidx.health.services.client.data.DeltaDataType<?,?> dataType, androidx.health.services.client.MeasureCallback callback, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+  }
+
+  @kotlin.jvm.JvmDefaultWithCompatibility public interface PassiveListenerCallback {
+    method public default void onGoalCompleted(androidx.health.services.client.data.PassiveGoal goal);
+    method public default void onHealthEventReceived(androidx.health.services.client.data.HealthEvent event);
+    method public default void onNewDataPointsReceived(androidx.health.services.client.data.DataPointContainer dataPoints);
+    method public default void onPermissionLost();
+    method public default void onRegistered();
+    method public default void onRegistrationFailed(Throwable throwable);
+    method public default void onUserActivityInfoReceived(androidx.health.services.client.data.UserActivityInfo info);
+  }
+
+  public abstract class PassiveListenerService extends android.app.Service {
+    ctor public PassiveListenerService();
+    method public final android.os.IBinder? onBind(android.content.Intent intent);
+    method public void onGoalCompleted(androidx.health.services.client.data.PassiveGoal goal);
+    method public void onHealthEventReceived(androidx.health.services.client.data.HealthEvent event);
+    method public void onNewDataPointsReceived(androidx.health.services.client.data.DataPointContainer dataPoints);
+    method public void onPermissionLost();
+    method public void onUserActivityInfoReceived(androidx.health.services.client.data.UserActivityInfo info);
+  }
+
+  public interface PassiveMonitoringClient {
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearPassiveListenerCallbackAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearPassiveListenerServiceAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> flushAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> getCapabilitiesAsync();
+    method public void setPassiveListenerCallback(androidx.health.services.client.data.PassiveListenerConfig config, androidx.health.services.client.PassiveListenerCallback callback);
+    method public void setPassiveListenerCallback(androidx.health.services.client.data.PassiveListenerConfig config, java.util.concurrent.Executor executor, androidx.health.services.client.PassiveListenerCallback callback);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setPassiveListenerServiceAsync(Class<? extends androidx.health.services.client.PassiveListenerService> service, androidx.health.services.client.data.PassiveListenerConfig config);
+  }
+
+  public final class PassiveMonitoringClientExtensionKt {
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? clearPassiveListenerCallback(androidx.health.services.client.PassiveMonitoringClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? clearPassiveListenerService(androidx.health.services.client.PassiveMonitoringClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? flush(androidx.health.services.client.PassiveMonitoringClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? getCapabilities(androidx.health.services.client.PassiveMonitoringClient, kotlin.coroutines.Continuation<? super androidx.health.services.client.data.PassiveMonitoringCapabilities>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? setPassiveListenerService(androidx.health.services.client.PassiveMonitoringClient, Class<? extends androidx.health.services.client.PassiveListenerService> service, androidx.health.services.client.data.PassiveListenerConfig config, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+  }
+
+}
+
+package androidx.health.services.client.data {
+
+  public final class AggregateDataType<T extends java.lang.Number, D extends androidx.health.services.client.data.DataPoint<T>> extends androidx.health.services.client.data.DataType<T,D> {
+    ctor public AggregateDataType(String name, androidx.health.services.client.data.DataType.TimeType timeType, Class<T> valueClass);
+  }
+
+  @kotlin.jvm.JvmDefaultWithCompatibility public interface Availability {
+    method public int getId();
+    property public abstract int id;
+    field public static final androidx.health.services.client.data.Availability.Companion Companion;
+  }
+
+  public static final class Availability.Companion {
+  }
+
+  public final class BatchingMode {
+    field public static final androidx.health.services.client.data.BatchingMode.Companion Companion;
+    field public static final androidx.health.services.client.data.BatchingMode HEART_RATE_5_SECONDS;
+  }
+
+  public static final class BatchingMode.Companion {
+  }
+
+  public final class ComparisonType {
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.ComparisonType.Companion Companion;
+    field public static final androidx.health.services.client.data.ComparisonType GREATER_THAN;
+    field public static final androidx.health.services.client.data.ComparisonType GREATER_THAN_OR_EQUAL;
+    field public static final androidx.health.services.client.data.ComparisonType LESS_THAN;
+    field public static final androidx.health.services.client.data.ComparisonType LESS_THAN_OR_EQUAL;
+    field public static final androidx.health.services.client.data.ComparisonType UNKNOWN;
+  }
+
+  public static final class ComparisonType.Companion {
+  }
+
+  public final class CumulativeDataPoint<T extends java.lang.Number> extends androidx.health.services.client.data.DataPoint<T> {
+    ctor public CumulativeDataPoint(androidx.health.services.client.data.AggregateDataType<T,androidx.health.services.client.data.CumulativeDataPoint<T>> dataType, T total, java.time.Instant start, java.time.Instant end);
+    method public java.time.Instant getEnd();
+    method public java.time.Instant getStart();
+    method public T getTotal();
+    property public final java.time.Instant end;
+    property public final java.time.Instant start;
+    property public final T total;
+  }
+
+  public abstract class DataPoint<T> {
+    method public androidx.health.services.client.data.DataType<T,? extends androidx.health.services.client.data.DataPoint<T>> getDataType();
+    property public androidx.health.services.client.data.DataType<T,? extends androidx.health.services.client.data.DataPoint<T>> dataType;
+  }
+
+  public abstract class DataPointAccuracy {
+    ctor public DataPointAccuracy();
+  }
+
+  public final class DataPointContainer {
+    ctor public DataPointContainer(java.util.Map<androidx.health.services.client.data.DataType<?,?>,? extends java.util.List<? extends androidx.health.services.client.data.DataPoint<?>>> dataPoints);
+    ctor public DataPointContainer(java.util.List<? extends androidx.health.services.client.data.DataPoint<?>> dataPointList);
+    method public java.util.List<androidx.health.services.client.data.CumulativeDataPoint<?>> getCumulativeDataPoints();
+    method public <T, D extends androidx.health.services.client.data.DataPoint<T>> java.util.List<D> getData(androidx.health.services.client.data.DeltaDataType<T,D> type);
+    method public <T extends java.lang.Number, D extends androidx.health.services.client.data.DataPoint<T>> D? getData(androidx.health.services.client.data.AggregateDataType<T,D> type);
+    method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getDataTypes();
+    method public java.util.List<androidx.health.services.client.data.IntervalDataPoint<?>> getIntervalDataPoints();
+    method public java.util.List<androidx.health.services.client.data.SampleDataPoint<?>> getSampleDataPoints();
+    method public java.util.List<androidx.health.services.client.data.StatisticalDataPoint<?>> getStatisticalDataPoints();
+    property public final java.util.List<androidx.health.services.client.data.CumulativeDataPoint<?>> cumulativeDataPoints;
+    property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> dataTypes;
+    property public final java.util.List<androidx.health.services.client.data.IntervalDataPoint<?>> intervalDataPoints;
+    property public final java.util.List<androidx.health.services.client.data.SampleDataPoint<?>> sampleDataPoints;
+    property public final java.util.List<androidx.health.services.client.data.StatisticalDataPoint<?>> statisticalDataPoints;
+  }
+
+  public abstract class DataType<T, D extends androidx.health.services.client.data.DataPoint<T>> {
+    ctor public DataType(String name, androidx.health.services.client.data.DataType.TimeType timeType, Class<T> valueClass, boolean isAggregate);
+    method public final String getName();
+    method public final Class<T> getValueClass();
+    property public final String name;
+    property public final Class<T> valueClass;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> ABSOLUTE_ELEVATION;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> ABSOLUTE_ELEVATION_STATS;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> ACTIVE_EXERCISE_DURATION_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> CALORIES;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> CALORIES_DAILY;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> CALORIES_TOTAL;
+    field public static final androidx.health.services.client.data.DataType.Companion Companion;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> DECLINE_DISTANCE;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> DECLINE_DISTANCE_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> DECLINE_DURATION;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> DECLINE_DURATION_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> DISTANCE;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> DISTANCE_DAILY;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> DISTANCE_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> ELEVATION_GAIN;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> ELEVATION_GAIN_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> ELEVATION_LOSS;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> ELEVATION_LOSS_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> FLAT_GROUND_DISTANCE;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> FLAT_GROUND_DISTANCE_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> FLAT_GROUND_DURATION;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> FLAT_GROUND_DURATION_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> FLOORS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> FLOORS_DAILY;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> FLOORS_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> GOLF_SHOT_COUNT;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> GOLF_SHOT_COUNT_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> HEART_RATE_BPM;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> HEART_RATE_BPM_STATS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> INCLINE_DISTANCE;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> INCLINE_DISTANCE_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> INCLINE_DURATION;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> INCLINE_DURATION_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<androidx.health.services.client.data.LocationData,androidx.health.services.client.data.SampleDataPoint<androidx.health.services.client.data.LocationData>> LOCATION;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> PACE;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> PACE_STATS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> REP_COUNT;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> REP_COUNT_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> RESTING_EXERCISE_DURATION;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> RESTING_EXERCISE_DURATION_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> RUNNING_STEPS;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> RUNNING_STEPS_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> SPEED;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> SPEED_STATS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> STEPS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> STEPS_DAILY;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.SampleDataPoint<java.lang.Long>> STEPS_PER_MINUTE;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Long>> STEPS_PER_MINUTE_STATS;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> STEPS_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> SWIMMING_LAP_COUNT;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> SWIMMING_STROKES;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> SWIMMING_STROKES_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> VO2_MAX;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> VO2_MAX_STATS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> WALKING_STEPS;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> WALKING_STEPS_TOTAL;
+  }
+
+  public static final class DataType.Companion {
+  }
+
+  public static final class DataType.TimeType {
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.DataType.TimeType.Companion Companion;
+    field public static final androidx.health.services.client.data.DataType.TimeType INTERVAL;
+    field public static final androidx.health.services.client.data.DataType.TimeType SAMPLE;
+    field public static final androidx.health.services.client.data.DataType.TimeType UNKNOWN;
+  }
+
+  public static final class DataType.TimeType.Companion {
+  }
+
+  public final class DataTypeAvailability implements androidx.health.services.client.data.Availability {
+    method public static androidx.health.services.client.data.DataTypeAvailability? fromId(int id);
+    method public int getId();
+    method public String getName();
+    property public int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.DataTypeAvailability ACQUIRING;
+    field public static final androidx.health.services.client.data.DataTypeAvailability AVAILABLE;
+    field public static final androidx.health.services.client.data.DataTypeAvailability.Companion Companion;
+    field public static final androidx.health.services.client.data.DataTypeAvailability UNAVAILABLE;
+    field public static final androidx.health.services.client.data.DataTypeAvailability UNAVAILABLE_DEVICE_OFF_BODY;
+    field public static final androidx.health.services.client.data.DataTypeAvailability UNKNOWN;
+  }
+
+  public static final class DataTypeAvailability.Companion {
+    method public androidx.health.services.client.data.DataTypeAvailability? fromId(int id);
+  }
+
+  public final class DataTypeCondition<T extends java.lang.Number, D extends androidx.health.services.client.data.DataType<T, ? extends androidx.health.services.client.data.DataPoint<T>>> {
+    ctor public DataTypeCondition(D dataType, T threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+    method public androidx.health.services.client.data.ComparisonType getComparisonType();
+    method public D getDataType();
+    method public T getThreshold();
+    property public final androidx.health.services.client.data.ComparisonType comparisonType;
+    property public final D dataType;
+    property public final T threshold;
+  }
+
+  public final class DeltaDataType<T, D extends androidx.health.services.client.data.DataPoint<T>> extends androidx.health.services.client.data.DataType<T,D> {
+    ctor public DeltaDataType(String name, androidx.health.services.client.data.DataType.TimeType timeType, Class<T> valueClass);
+  }
+
+  public final class ExerciseCapabilities {
+    ctor public ExerciseCapabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> typeToCapabilities, optional java.util.Set<androidx.health.services.client.data.BatchingMode> supportedBatchingModeOverrides);
+    ctor public ExerciseCapabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> typeToCapabilities);
+    method public java.util.Set<androidx.health.services.client.data.ExerciseType> getAutoPauseAndResumeEnabledExercises();
+    method public androidx.health.services.client.data.ExerciseTypeCapabilities getExerciseTypeCapabilities(androidx.health.services.client.data.ExerciseType exercise);
+    method public java.util.Set<androidx.health.services.client.data.BatchingMode> getSupportedBatchingModeOverrides();
+    method public java.util.Set<androidx.health.services.client.data.ExerciseType> getSupportedExerciseTypes();
+    method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> getTypeToCapabilities();
+    property public final java.util.Set<androidx.health.services.client.data.ExerciseType> autoPauseAndResumeEnabledExercises;
+    property public final java.util.Set<androidx.health.services.client.data.BatchingMode> supportedBatchingModeOverrides;
+    property public final java.util.Set<androidx.health.services.client.data.ExerciseType> supportedExerciseTypes;
+    property public final java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> typeToCapabilities;
+  }
+
+  public final class ExerciseConfig {
+    ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters, optional androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig, optional java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides);
+    ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters, optional androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig);
+    ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters);
+    method public static androidx.health.services.client.data.ExerciseConfig.Builder builder(androidx.health.services.client.data.ExerciseType exerciseType);
+    method public java.util.Set<androidx.health.services.client.data.BatchingMode> getBatchingModeOverrides();
+    method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getDataTypes();
+    method public java.util.List<androidx.health.services.client.data.ExerciseGoal<?>> getExerciseGoals();
+    method public android.os.Bundle getExerciseParams();
+    method public androidx.health.services.client.data.ExerciseType getExerciseType();
+    method public androidx.health.services.client.data.ExerciseTypeConfig? getExerciseTypeConfig();
+    method public float getSwimmingPoolLengthMeters();
+    method public boolean isAutoPauseAndResumeEnabled();
+    method public boolean isGpsEnabled();
+    property public final java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides;
+    property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> dataTypes;
+    property public final java.util.List<androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals;
+    property public final android.os.Bundle exerciseParams;
+    property public final androidx.health.services.client.data.ExerciseType exerciseType;
+    property public final androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig;
+    property public final boolean isAutoPauseAndResumeEnabled;
+    property public final boolean isGpsEnabled;
+    property public final float swimmingPoolLengthMeters;
+    field public static final androidx.health.services.client.data.ExerciseConfig.Companion Companion;
+    field public static final float SWIMMING_POOL_LENGTH_UNSPECIFIED = 0.0f;
+  }
+
+  public static final class ExerciseConfig.Builder {
+    ctor public ExerciseConfig.Builder(androidx.health.services.client.data.ExerciseType exerciseType);
+    method public androidx.health.services.client.data.ExerciseConfig build();
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setBatchingModeOverrides(java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setDataTypes(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseGoals(java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseParams(android.os.Bundle exerciseParams);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseTypeConfig(androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setIsAutoPauseAndResumeEnabled(boolean isAutoPauseAndResumeEnabled);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setIsGpsEnabled(boolean isGpsEnabled);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setSwimmingPoolLengthMeters(float swimmingPoolLength);
+  }
+
+  public static final class ExerciseConfig.Companion {
+    method public androidx.health.services.client.data.ExerciseConfig.Builder builder(androidx.health.services.client.data.ExerciseType exerciseType);
+  }
+
+  public final class ExerciseGoal<T extends java.lang.Number> implements android.os.Parcelable {
+    method public static <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createMilestone(androidx.health.services.client.data.DataTypeCondition<T,androidx.health.services.client.data.AggregateDataType<T,?>> condition, T period);
+    method public static <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal<T> goal, T newThreshold);
+    method public static <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition<T,androidx.health.services.client.data.AggregateDataType<T,?>> condition);
+    method public int describeContents();
+    method public androidx.health.services.client.data.DataTypeCondition<T,androidx.health.services.client.data.AggregateDataType<T,?>> getDataTypeCondition();
+    method public androidx.health.services.client.data.ExerciseGoalType getExerciseGoalType();
+    method public T? getPeriod();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.DataTypeCondition<T,androidx.health.services.client.data.AggregateDataType<T,?>> dataTypeCondition;
+    property public final androidx.health.services.client.data.ExerciseGoalType exerciseGoalType;
+    property public final T? period;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseGoal<?>> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseGoal.Companion Companion;
+  }
+
+  public static final class ExerciseGoal.Companion {
+    method public <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createMilestone(androidx.health.services.client.data.DataTypeCondition<T,androidx.health.services.client.data.AggregateDataType<T,?>> condition, T period);
+    method public <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal<T> goal, T newThreshold);
+    method public <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition<T,androidx.health.services.client.data.AggregateDataType<T,?>> condition);
+  }
+
+  public final class ExerciseGoalType {
+    method public static androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.ExerciseGoalType.Companion Companion;
+    field public static final androidx.health.services.client.data.ExerciseGoalType MILESTONE;
+    field public static final androidx.health.services.client.data.ExerciseGoalType ONE_TIME_GOAL;
+  }
+
+  public static final class ExerciseGoalType.Companion {
+    method public androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+  }
+
+  public final class ExerciseInfo {
+    ctor public ExerciseInfo(int exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+    method public int getExerciseTrackedStatus();
+    method public androidx.health.services.client.data.ExerciseType getExerciseType();
+    property public final int exerciseTrackedStatus;
+    property public final androidx.health.services.client.data.ExerciseType exerciseType;
+  }
+
+  public final class ExerciseLapSummary {
+    ctor public ExerciseLapSummary(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.DataPointContainer lapMetrics);
+    method public java.time.Duration getActiveDuration();
+    method public java.time.Instant getEndTime();
+    method public int getLapCount();
+    method public androidx.health.services.client.data.DataPointContainer getLapMetrics();
+    method public java.time.Instant getStartTime();
+    property public final java.time.Duration activeDuration;
+    property public final java.time.Instant endTime;
+    property public final int lapCount;
+    property public final androidx.health.services.client.data.DataPointContainer lapMetrics;
+    property public final java.time.Instant startTime;
+  }
+
+  public final class ExerciseState {
+    method public static androidx.health.services.client.data.ExerciseState? fromId(int id);
+    method public int getId();
+    method public String getName();
+    method public boolean isEnded();
+    method public boolean isEnding();
+    method public boolean isPaused();
+    method public boolean isResuming();
+    property public final int id;
+    property public final boolean isEnded;
+    property public final boolean isEnding;
+    property public final boolean isPaused;
+    property public final boolean isResuming;
+    property public final String name;
+    field public static final androidx.health.services.client.data.ExerciseState ACTIVE;
+    field public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSED;
+    field public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSING;
+    field public static final androidx.health.services.client.data.ExerciseState AUTO_RESUMING;
+    field public static final androidx.health.services.client.data.ExerciseState.Companion Companion;
+    field public static final androidx.health.services.client.data.ExerciseState ENDED;
+    field public static final androidx.health.services.client.data.ExerciseState ENDING;
+    field public static final androidx.health.services.client.data.ExerciseState PREPARING;
+    field public static final androidx.health.services.client.data.ExerciseState USER_PAUSED;
+    field public static final androidx.health.services.client.data.ExerciseState USER_PAUSING;
+    field public static final androidx.health.services.client.data.ExerciseState USER_RESUMING;
+    field public static final androidx.health.services.client.data.ExerciseState USER_STARTING;
+  }
+
+  public static final class ExerciseState.Companion {
+    method public androidx.health.services.client.data.ExerciseState? fromId(int id);
+  }
+
+  public final class ExerciseStateInfo {
+    ctor public ExerciseStateInfo(androidx.health.services.client.data.ExerciseState exerciseState, int exerciseEndReason);
+    method public int getEndReason();
+    method public androidx.health.services.client.data.ExerciseState getState();
+    property public final int endReason;
+    property public final androidx.health.services.client.data.ExerciseState state;
+    field public static final androidx.health.services.client.data.ExerciseStateInfo.Companion Companion;
+  }
+
+  public static final class ExerciseStateInfo.Companion {
+  }
+
+  public final class ExerciseType {
+    method public static androidx.health.services.client.data.ExerciseType fromId(int id);
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.ExerciseType ALPINE_SKIING;
+    field public static final androidx.health.services.client.data.ExerciseType BACKPACKING;
+    field public static final androidx.health.services.client.data.ExerciseType BACK_EXTENSION;
+    field public static final androidx.health.services.client.data.ExerciseType BADMINTON;
+    field public static final androidx.health.services.client.data.ExerciseType BARBELL_SHOULDER_PRESS;
+    field public static final androidx.health.services.client.data.ExerciseType BASEBALL;
+    field public static final androidx.health.services.client.data.ExerciseType BASKETBALL;
+    field public static final androidx.health.services.client.data.ExerciseType BENCH_PRESS;
+    field public static final androidx.health.services.client.data.ExerciseType BIKING;
+    field public static final androidx.health.services.client.data.ExerciseType BIKING_STATIONARY;
+    field public static final androidx.health.services.client.data.ExerciseType BOOT_CAMP;
+    field public static final androidx.health.services.client.data.ExerciseType BOXING;
+    field public static final androidx.health.services.client.data.ExerciseType BURPEE;
+    field public static final androidx.health.services.client.data.ExerciseType CALISTHENICS;
+    field public static final androidx.health.services.client.data.ExerciseType CRICKET;
+    field public static final androidx.health.services.client.data.ExerciseType CROSS_COUNTRY_SKIING;
+    field public static final androidx.health.services.client.data.ExerciseType CRUNCH;
+    field public static final androidx.health.services.client.data.ExerciseType.Companion Companion;
+    field public static final androidx.health.services.client.data.ExerciseType DANCING;
+    field public static final androidx.health.services.client.data.ExerciseType DEADLIFT;
+    field public static final androidx.health.services.client.data.ExerciseType ELLIPTICAL;
+    field public static final androidx.health.services.client.data.ExerciseType EXERCISE_CLASS;
+    field public static final androidx.health.services.client.data.ExerciseType FENCING;
+    field public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AMERICAN;
+    field public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AUSTRALIAN;
+    field public static final androidx.health.services.client.data.ExerciseType FORWARD_TWIST;
+    field public static final androidx.health.services.client.data.ExerciseType FRISBEE_DISC;
+    field public static final androidx.health.services.client.data.ExerciseType GOLF;
+    field public static final androidx.health.services.client.data.ExerciseType GUIDED_BREATHING;
+    field public static final androidx.health.services.client.data.ExerciseType GYMNASTICS;
+    field public static final androidx.health.services.client.data.ExerciseType HANDBALL;
+    field public static final androidx.health.services.client.data.ExerciseType HIGH_INTENSITY_INTERVAL_TRAINING;
+    field public static final androidx.health.services.client.data.ExerciseType HIKING;
+    field public static final androidx.health.services.client.data.ExerciseType HORSE_RIDING;
+    field public static final androidx.health.services.client.data.ExerciseType ICE_HOCKEY;
+    field public static final androidx.health.services.client.data.ExerciseType ICE_SKATING;
+    field public static final androidx.health.services.client.data.ExerciseType INLINE_SKATING;
+    field public static final androidx.health.services.client.data.ExerciseType JUMPING_JACK;
+    field public static final androidx.health.services.client.data.ExerciseType JUMP_ROPE;
+    field public static final androidx.health.services.client.data.ExerciseType LAT_PULL_DOWN;
+    field public static final androidx.health.services.client.data.ExerciseType LUNGE;
+    field public static final androidx.health.services.client.data.ExerciseType MARTIAL_ARTS;
+    field public static final androidx.health.services.client.data.ExerciseType MEDITATION;
+    field public static final androidx.health.services.client.data.ExerciseType MOUNTAIN_BIKING;
+    field public static final androidx.health.services.client.data.ExerciseType ORIENTEERING;
+    field public static final androidx.health.services.client.data.ExerciseType PADDLING;
+    field public static final androidx.health.services.client.data.ExerciseType PARA_GLIDING;
+    field public static final androidx.health.services.client.data.ExerciseType PILATES;
+    field public static final androidx.health.services.client.data.ExerciseType PLANK;
+    field public static final androidx.health.services.client.data.ExerciseType RACQUETBALL;
+    field public static final androidx.health.services.client.data.ExerciseType ROCK_CLIMBING;
+    field public static final androidx.health.services.client.data.ExerciseType ROLLER_HOCKEY;
+    field public static final androidx.health.services.client.data.ExerciseType ROLLER_SKATING;
+    field public static final androidx.health.services.client.data.ExerciseType ROWING;
+    field public static final androidx.health.services.client.data.ExerciseType ROWING_MACHINE;
+    field public static final androidx.health.services.client.data.ExerciseType RUGBY;
+    field public static final androidx.health.services.client.data.ExerciseType RUNNING;
+    field public static final androidx.health.services.client.data.ExerciseType RUNNING_TREADMILL;
+    field public static final androidx.health.services.client.data.ExerciseType SAILING;
+    field public static final androidx.health.services.client.data.ExerciseType SCUBA_DIVING;
+    field public static final androidx.health.services.client.data.ExerciseType SKATING;
+    field public static final androidx.health.services.client.data.ExerciseType SKIING;
+    field public static final androidx.health.services.client.data.ExerciseType SNOWBOARDING;
+    field public static final androidx.health.services.client.data.ExerciseType SNOWSHOEING;
+    field public static final androidx.health.services.client.data.ExerciseType SOCCER;
+    field public static final androidx.health.services.client.data.ExerciseType SOFTBALL;
+    field public static final androidx.health.services.client.data.ExerciseType SQUASH;
+    field public static final androidx.health.services.client.data.ExerciseType SQUAT;
+    field public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING;
+    field public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING_MACHINE;
+    field public static final androidx.health.services.client.data.ExerciseType STRENGTH_TRAINING;
+    field public static final androidx.health.services.client.data.ExerciseType STRETCHING;
+    field public static final androidx.health.services.client.data.ExerciseType SURFING;
+    field public static final androidx.health.services.client.data.ExerciseType SWIMMING_OPEN_WATER;
+    field public static final androidx.health.services.client.data.ExerciseType SWIMMING_POOL;
+    field public static final androidx.health.services.client.data.ExerciseType TABLE_TENNIS;
+    field public static final androidx.health.services.client.data.ExerciseType TENNIS;
+    field public static final androidx.health.services.client.data.ExerciseType UNKNOWN;
+    field public static final androidx.health.services.client.data.ExerciseType UPPER_TWIST;
+    field public static final androidx.health.services.client.data.ExerciseType VOLLEYBALL;
+    field public static final androidx.health.services.client.data.ExerciseType WALKING;
+    field public static final androidx.health.services.client.data.ExerciseType WATER_POLO;
+    field public static final androidx.health.services.client.data.ExerciseType WEIGHTLIFTING;
+    field public static final androidx.health.services.client.data.ExerciseType WORKOUT;
+    field public static final androidx.health.services.client.data.ExerciseType YACHTING;
+    field public static final androidx.health.services.client.data.ExerciseType YOGA;
+  }
+
+  public static final class ExerciseType.Companion {
+    method public androidx.health.services.client.data.ExerciseType fromId(int id);
+  }
+
+  public final class ExerciseTypeCapabilities {
+    ctor public ExerciseTypeCapabilities(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> supportedDataTypes, java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,? extends java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,? extends java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume);
+    method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getSupportedDataTypes();
+    method public java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedGoals();
+    method public java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedMilestones();
+    method public boolean getSupportsAutoPauseAndResume();
+    property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> supportedDataTypes;
+    property public final java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals;
+    property public final java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones;
+    property public final boolean supportsAutoPauseAndResume;
+  }
+
+  public abstract class ExerciseTypeConfig {
+    field public static final androidx.health.services.client.data.ExerciseTypeConfig.Companion Companion;
+  }
+
+  public static final class ExerciseTypeConfig.Companion {
+  }
+
+  public final class ExerciseUpdate {
+    method public java.time.Duration getActiveDurationAtDataPoint(androidx.health.services.client.data.IntervalDataPoint<?> dataPoint);
+    method public java.time.Duration getActiveDurationAtDataPoint(androidx.health.services.client.data.SampleDataPoint<?> dataPoint);
+    method public androidx.health.services.client.data.ExerciseUpdate.ActiveDurationCheckpoint? getActiveDurationCheckpoint();
+    method public androidx.health.services.client.data.ExerciseConfig? getExerciseConfig();
+    method public androidx.health.services.client.data.ExerciseStateInfo getExerciseStateInfo();
+    method public java.util.Set<androidx.health.services.client.data.ExerciseGoal<? extends java.lang.Number>> getLatestAchievedGoals();
+    method public androidx.health.services.client.data.DataPointContainer getLatestMetrics();
+    method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> getLatestMilestoneMarkerSummaries();
+    method public java.time.Instant? getStartTime();
+    method public java.time.Duration getUpdateDurationFromBoot();
+    property public final androidx.health.services.client.data.ExerciseUpdate.ActiveDurationCheckpoint? activeDurationCheckpoint;
+    property public final androidx.health.services.client.data.ExerciseConfig? exerciseConfig;
+    property public final androidx.health.services.client.data.ExerciseStateInfo exerciseStateInfo;
+    property public final java.util.Set<androidx.health.services.client.data.ExerciseGoal<? extends java.lang.Number>> latestAchievedGoals;
+    property public final androidx.health.services.client.data.DataPointContainer latestMetrics;
+    property public final java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries;
+    property public final java.time.Instant? startTime;
+    field public static final androidx.health.services.client.data.ExerciseUpdate.Companion Companion;
+  }
+
+  public static final class ExerciseUpdate.ActiveDurationCheckpoint {
+    ctor public ExerciseUpdate.ActiveDurationCheckpoint(java.time.Instant time, java.time.Duration activeDuration);
+    method public java.time.Duration getActiveDuration();
+    method public java.time.Instant getTime();
+    property public final java.time.Duration activeDuration;
+    property public final java.time.Instant time;
+  }
+
+  public static final class ExerciseUpdate.Companion {
+  }
+
+  public final class GolfExerciseTypeConfig extends androidx.health.services.client.data.ExerciseTypeConfig {
+    ctor public GolfExerciseTypeConfig(optional androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo golfShotTrackingPlaceInfo);
+    method public androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo getGolfShotTrackingPlaceInfo();
+    property public final androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo golfShotTrackingPlaceInfo;
+  }
+
+  public static final class GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo {
+    method public int getPlaceInfoId();
+    property public final int placeInfoId;
+    field public static final androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo.Companion Companion;
+    field public static final androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo GOLF_SHOT_TRACKING_PLACE_INFO_FAIRWAY;
+    field public static final androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo GOLF_SHOT_TRACKING_PLACE_INFO_PUTTING_GREEN;
+    field public static final androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo GOLF_SHOT_TRACKING_PLACE_INFO_TEE_BOX;
+    field public static final androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo GOLF_SHOT_TRACKING_PLACE_INFO_UNSPECIFIED;
+  }
+
+  public static final class GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo.Companion {
+  }
+
+  public final class HealthEvent {
+    ctor public HealthEvent(androidx.health.services.client.data.HealthEvent.Type type, java.time.Instant eventTime, androidx.health.services.client.data.DataPointContainer metrics);
+    method public java.time.Instant getEventTime();
+    method public androidx.health.services.client.data.DataPointContainer getMetrics();
+    method public androidx.health.services.client.data.HealthEvent.Type getType();
+    property public final java.time.Instant eventTime;
+    property public final androidx.health.services.client.data.DataPointContainer metrics;
+    property public final androidx.health.services.client.data.HealthEvent.Type type;
+  }
+
+  public static final class HealthEvent.Type {
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.HealthEvent.Type.Companion Companion;
+    field public static final androidx.health.services.client.data.HealthEvent.Type FALL_DETECTED;
+    field public static final androidx.health.services.client.data.HealthEvent.Type UNKNOWN;
+  }
+
+  public static final class HealthEvent.Type.Companion {
+  }
+
+  public final class HeartRateAccuracy extends androidx.health.services.client.data.DataPointAccuracy {
+    ctor public HeartRateAccuracy(androidx.health.services.client.data.HeartRateAccuracy.SensorStatus sensorStatus);
+    method public androidx.health.services.client.data.HeartRateAccuracy.SensorStatus getSensorStatus();
+    property public final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus sensorStatus;
+  }
+
+  public static final class HeartRateAccuracy.SensorStatus {
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus ACCURACY_HIGH;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus ACCURACY_LOW;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus ACCURACY_MEDIUM;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus.Companion Companion;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus NO_CONTACT;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus UNKNOWN;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus UNRELIABLE;
+  }
+
+  public static final class HeartRateAccuracy.SensorStatus.Companion {
+  }
+
+  public final class IntervalDataPoint<T> extends androidx.health.services.client.data.DataPoint<T> {
+    ctor public IntervalDataPoint(androidx.health.services.client.data.DataType<T,? extends androidx.health.services.client.data.IntervalDataPoint<T>> dataType, T value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata, optional androidx.health.services.client.data.DataPointAccuracy? accuracy);
+    method public androidx.health.services.client.data.DataPointAccuracy? getAccuracy();
+    method public androidx.health.services.client.data.DataType<T,? extends androidx.health.services.client.data.IntervalDataPoint<T>> getDataType();
+    method public java.time.Duration getEndDurationFromBoot();
+    method public java.time.Instant getEndInstant(java.time.Instant bootInstant);
+    method public android.os.Bundle getMetadata();
+    method public java.time.Duration getStartDurationFromBoot();
+    method public java.time.Instant getStartInstant(java.time.Instant bootInstant);
+    method public T getValue();
+    property public final androidx.health.services.client.data.DataPointAccuracy? accuracy;
+    property public androidx.health.services.client.data.DataType<T,? extends androidx.health.services.client.data.IntervalDataPoint<T>> dataType;
+    property public final java.time.Duration endDurationFromBoot;
+    property public final android.os.Bundle metadata;
+    property public final java.time.Duration startDurationFromBoot;
+    property public final T value;
+  }
+
+  public final class LocationAccuracy extends androidx.health.services.client.data.DataPointAccuracy {
+    ctor public LocationAccuracy(@FloatRange(from=0.0) double horizontalPositionErrorMeters, optional @FloatRange(from=0.0) double verticalPositionErrorMeters);
+    method public double getHorizontalPositionErrorMeters();
+    method public double getVerticalPositionErrorMeters();
+    property public final double horizontalPositionErrorMeters;
+    property public final double verticalPositionErrorMeters;
+    field public static final androidx.health.services.client.data.LocationAccuracy.Companion Companion;
+  }
+
+  public static final class LocationAccuracy.Companion {
+  }
+
+  public final class LocationAvailability implements androidx.health.services.client.data.Availability {
+    method public static androidx.health.services.client.data.LocationAvailability? fromId(int id);
+    method public int getId();
+    method public String getName();
+    property public int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.LocationAvailability ACQUIRED_TETHERED;
+    field public static final androidx.health.services.client.data.LocationAvailability ACQUIRED_UNTETHERED;
+    field public static final androidx.health.services.client.data.LocationAvailability ACQUIRING;
+    field public static final androidx.health.services.client.data.LocationAvailability.Companion Companion;
+    field public static final androidx.health.services.client.data.LocationAvailability NO_GNSS;
+    field public static final androidx.health.services.client.data.LocationAvailability UNAVAILABLE;
+    field public static final androidx.health.services.client.data.LocationAvailability UNKNOWN;
+  }
+
+  public static final class LocationAvailability.Companion {
+    method public androidx.health.services.client.data.LocationAvailability? fromId(int id);
+  }
+
+  public final class LocationData {
+    ctor public LocationData(@FloatRange(from=-90.0, to=90.0) double latitude, @FloatRange(from=-180.0, to=180.0) double longitude, optional double altitude, optional double bearing);
+    method public double getAltitude();
+    method public double getBearing();
+    method public double getLatitude();
+    method public double getLongitude();
+    property public final double altitude;
+    property public final double bearing;
+    property public final double latitude;
+    property public final double longitude;
+    field public static final double ALTITUDE_UNAVAILABLE = (0.0/0.0);
+    field public static final double BEARING_UNAVAILABLE = (0.0/0.0);
+  }
+
+  public final class MeasureCapabilities {
+    ctor public MeasureCapabilities(java.util.Set<? extends androidx.health.services.client.data.DeltaDataType<?,?>> supportedDataTypesMeasure);
+    method public java.util.Set<androidx.health.services.client.data.DeltaDataType<?,?>> getSupportedDataTypesMeasure();
+    property public final java.util.Set<androidx.health.services.client.data.DeltaDataType<?,?>> supportedDataTypesMeasure;
+  }
+
+  public final class MilestoneMarkerSummary {
+    ctor public MilestoneMarkerSummary(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.ExerciseGoal<? extends java.lang.Number> achievedGoal, androidx.health.services.client.data.DataPointContainer summaryMetrics);
+    method public androidx.health.services.client.data.ExerciseGoal<? extends java.lang.Number> getAchievedGoal();
+    method public java.time.Duration getActiveDuration();
+    method public java.time.Instant getEndTime();
+    method public java.time.Instant getStartTime();
+    method public androidx.health.services.client.data.DataPointContainer getSummaryMetrics();
+    property public final androidx.health.services.client.data.ExerciseGoal<? extends java.lang.Number> achievedGoal;
+    property public final java.time.Duration activeDuration;
+    property public final java.time.Instant endTime;
+    property public final java.time.Instant startTime;
+    property public final androidx.health.services.client.data.DataPointContainer summaryMetrics;
+  }
+
+  public final class PassiveGoal {
+    ctor public PassiveGoal(androidx.health.services.client.data.DataTypeCondition<? extends java.lang.Number,? extends androidx.health.services.client.data.DeltaDataType<? extends java.lang.Number,?>> dataTypeCondition);
+    method public androidx.health.services.client.data.DataTypeCondition<? extends java.lang.Number,? extends androidx.health.services.client.data.DeltaDataType<? extends java.lang.Number,?>> getDataTypeCondition();
+    property public final androidx.health.services.client.data.DataTypeCondition<? extends java.lang.Number,? extends androidx.health.services.client.data.DeltaDataType<? extends java.lang.Number,?>> dataTypeCondition;
+  }
+
+  public final class PassiveListenerConfig {
+    ctor public PassiveListenerConfig(java.util.Set<? extends androidx.health.services.client.data.DataType<? extends java.lang.Object,? extends androidx.health.services.client.data.DataPoint<?>>> dataTypes, boolean shouldUserActivityInfoBeRequested, java.util.Set<androidx.health.services.client.data.PassiveGoal> dailyGoals, java.util.Set<androidx.health.services.client.data.HealthEvent.Type> healthEventTypes);
+    method public static androidx.health.services.client.data.PassiveListenerConfig.Builder builder();
+    method public java.util.Set<androidx.health.services.client.data.PassiveGoal> getDailyGoals();
+    method public java.util.Set<androidx.health.services.client.data.DataType<? extends java.lang.Object,? extends androidx.health.services.client.data.DataPoint<?>>> getDataTypes();
+    method public java.util.Set<androidx.health.services.client.data.HealthEvent.Type> getHealthEventTypes();
+    method public boolean getShouldUserActivityInfoBeRequested();
+    property public final java.util.Set<androidx.health.services.client.data.PassiveGoal> dailyGoals;
+    property public final java.util.Set<androidx.health.services.client.data.DataType<? extends java.lang.Object,? extends androidx.health.services.client.data.DataPoint<?>>> dataTypes;
+    property public final java.util.Set<androidx.health.services.client.data.HealthEvent.Type> healthEventTypes;
+    property public final boolean shouldUserActivityInfoBeRequested;
+    field public static final androidx.health.services.client.data.PassiveListenerConfig.Companion Companion;
+  }
+
+  public static final class PassiveListenerConfig.Builder {
+    ctor public PassiveListenerConfig.Builder();
+    method public androidx.health.services.client.data.PassiveListenerConfig build();
+    method public androidx.health.services.client.data.PassiveListenerConfig.Builder setDailyGoals(java.util.Set<androidx.health.services.client.data.PassiveGoal> dailyGoals);
+    method public androidx.health.services.client.data.PassiveListenerConfig.Builder setDataTypes(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes);
+    method public androidx.health.services.client.data.PassiveListenerConfig.Builder setHealthEventTypes(java.util.Set<androidx.health.services.client.data.HealthEvent.Type> healthEventTypes);
+    method public androidx.health.services.client.data.PassiveListenerConfig.Builder setShouldUserActivityInfoBeRequested(boolean shouldUserActivityInfoBeRequested);
+  }
+
+  public static final class PassiveListenerConfig.Companion {
+    method public androidx.health.services.client.data.PassiveListenerConfig.Builder builder();
+  }
+
+  public final class PassiveMonitoringCapabilities {
+    ctor public PassiveMonitoringCapabilities(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> supportedDataTypesPassiveMonitoring, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> supportedDataTypesPassiveGoals, java.util.Set<androidx.health.services.client.data.HealthEvent.Type> supportedHealthEventTypes, java.util.Set<androidx.health.services.client.data.UserActivityState> supportedUserActivityStates);
+    method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getSupportedDataTypesPassiveGoals();
+    method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getSupportedDataTypesPassiveMonitoring();
+    method public java.util.Set<androidx.health.services.client.data.HealthEvent.Type> getSupportedHealthEventTypes();
+    method public java.util.Set<androidx.health.services.client.data.UserActivityState> getSupportedUserActivityStates();
+    property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> supportedDataTypesPassiveGoals;
+    property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> supportedDataTypesPassiveMonitoring;
+    property public final java.util.Set<androidx.health.services.client.data.HealthEvent.Type> supportedHealthEventTypes;
+    property public final java.util.Set<androidx.health.services.client.data.UserActivityState> supportedUserActivityStates;
+  }
+
+  public final class PassiveMonitoringUpdate {
+    ctor public PassiveMonitoringUpdate(androidx.health.services.client.data.DataPointContainer dataPoints, java.util.List<androidx.health.services.client.data.UserActivityInfo> userActivityInfoUpdates);
+    method public androidx.health.services.client.data.DataPointContainer getDataPoints();
+    method public java.util.List<androidx.health.services.client.data.UserActivityInfo> getUserActivityInfoUpdates();
+    property public final androidx.health.services.client.data.DataPointContainer dataPoints;
+    property public final java.util.List<androidx.health.services.client.data.UserActivityInfo> userActivityInfoUpdates;
+  }
+
+  public final class SampleDataPoint<T> extends androidx.health.services.client.data.DataPoint<T> {
+    ctor public SampleDataPoint(androidx.health.services.client.data.DataType<T,androidx.health.services.client.data.SampleDataPoint<T>> dataType, T value, java.time.Duration timeDurationFromBoot, optional android.os.Bundle metadata, optional androidx.health.services.client.data.DataPointAccuracy? accuracy);
+    method public androidx.health.services.client.data.DataPointAccuracy? getAccuracy();
+    method public androidx.health.services.client.data.DataType<T,androidx.health.services.client.data.SampleDataPoint<T>> getDataType();
+    method public android.os.Bundle getMetadata();
+    method public java.time.Duration getTimeDurationFromBoot();
+    method public java.time.Instant getTimeInstant(java.time.Instant bootInstant);
+    method public T getValue();
+    property public final androidx.health.services.client.data.DataPointAccuracy? accuracy;
+    property public androidx.health.services.client.data.DataType<T,androidx.health.services.client.data.SampleDataPoint<T>> dataType;
+    property public final android.os.Bundle metadata;
+    property public final java.time.Duration timeDurationFromBoot;
+    property public final T value;
+  }
+
+  public final class StatisticalDataPoint<T extends java.lang.Number> extends androidx.health.services.client.data.DataPoint<T> {
+    ctor public StatisticalDataPoint(androidx.health.services.client.data.AggregateDataType<T,androidx.health.services.client.data.StatisticalDataPoint<T>> dataType, T min, T max, T average, java.time.Instant start, java.time.Instant end);
+    method public T getAverage();
+    method public java.time.Instant getEnd();
+    method public T getMax();
+    method public T getMin();
+    method public java.time.Instant getStart();
+    property public final T average;
+    property public final java.time.Instant end;
+    property public final T max;
+    property public final T min;
+    property public final java.time.Instant start;
+    field public static final androidx.health.services.client.data.StatisticalDataPoint.Companion Companion;
+  }
+
+  public static final class StatisticalDataPoint.Companion {
+  }
+
+  public final class UserActivityInfo {
+    ctor public UserActivityInfo(androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseInfo? exerciseInfo, java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.UserActivityInfo createActiveExerciseState(androidx.health.services.client.data.ExerciseInfo exerciseInfo, java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.UserActivityInfo createAsleepState(java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.UserActivityInfo createPassiveActivityState(java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.UserActivityInfo createUnknownTypeState(java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.ExerciseInfo? getExerciseInfo();
+    method public java.time.Instant getStateChangeTime();
+    method public androidx.health.services.client.data.UserActivityState getUserActivityState();
+    property public final androidx.health.services.client.data.ExerciseInfo? exerciseInfo;
+    property public final java.time.Instant stateChangeTime;
+    property public final androidx.health.services.client.data.UserActivityState userActivityState;
+    field public static final androidx.health.services.client.data.UserActivityInfo.Companion Companion;
+  }
+
+  public static final class UserActivityInfo.Companion {
+    method public androidx.health.services.client.data.UserActivityInfo createActiveExerciseState(androidx.health.services.client.data.ExerciseInfo exerciseInfo, java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.UserActivityInfo createAsleepState(java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.UserActivityInfo createPassiveActivityState(java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.UserActivityInfo createUnknownTypeState(java.time.Instant stateChangeTime);
+  }
+
+  public final class UserActivityState {
+    ctor public UserActivityState(int id, String name);
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.UserActivityState.Companion Companion;
+    field public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_ASLEEP;
+    field public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_EXERCISE;
+    field public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_PASSIVE;
+    field public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_UNKNOWN;
+  }
+
+  public static final class UserActivityState.Companion {
+  }
+
+  public final class WarmUpConfig {
+    ctor public WarmUpConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DeltaDataType<?,?>> dataTypes);
+    method public java.util.Set<androidx.health.services.client.data.DeltaDataType<?,?>> getDataTypes();
+    method public androidx.health.services.client.data.ExerciseType getExerciseType();
+    property public final java.util.Set<androidx.health.services.client.data.DeltaDataType<?,?>> dataTypes;
+    property public final androidx.health.services.client.data.ExerciseType exerciseType;
+  }
+
+}
+
diff --git a/health/health-services-client/api/public_plus_experimental_current.txt b/health/health-services-client/api/public_plus_experimental_current.txt
index b985692..9d0902d 100644
--- a/health/health-services-client/api/public_plus_experimental_current.txt
+++ b/health/health-services-client/api/public_plus_experimental_current.txt
@@ -10,6 +10,7 @@
     method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfoAsync();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLapAsync();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideAutoPauseAndResumeForActiveExerciseAsync(boolean enabled);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideBatchingModesForActiveExerciseAsync(java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModes);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> pauseExerciseAsync();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> prepareExerciseAsync(androidx.health.services.client.data.WarmUpConfig configuration);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> removeGoalFromActiveExerciseAsync(androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal);
@@ -141,6 +142,14 @@
   public static final class Availability.Companion {
   }
 
+  public final class BatchingMode {
+    field public static final androidx.health.services.client.data.BatchingMode.Companion Companion;
+    field public static final androidx.health.services.client.data.BatchingMode HEART_RATE_5_SECONDS;
+  }
+
+  public static final class BatchingMode.Companion {
+  }
+
   public final class ComparisonType {
     method public int getId();
     method public String getName();
@@ -307,20 +316,25 @@
   }
 
   public final class ExerciseCapabilities {
+    ctor public ExerciseCapabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> typeToCapabilities, optional java.util.Set<androidx.health.services.client.data.BatchingMode> supportedBatchingModeOverrides);
     ctor public ExerciseCapabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> typeToCapabilities);
     method public java.util.Set<androidx.health.services.client.data.ExerciseType> getAutoPauseAndResumeEnabledExercises();
     method public androidx.health.services.client.data.ExerciseTypeCapabilities getExerciseTypeCapabilities(androidx.health.services.client.data.ExerciseType exercise);
+    method public java.util.Set<androidx.health.services.client.data.BatchingMode> getSupportedBatchingModeOverrides();
     method public java.util.Set<androidx.health.services.client.data.ExerciseType> getSupportedExerciseTypes();
     method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> getTypeToCapabilities();
     property public final java.util.Set<androidx.health.services.client.data.ExerciseType> autoPauseAndResumeEnabledExercises;
+    property public final java.util.Set<androidx.health.services.client.data.BatchingMode> supportedBatchingModeOverrides;
     property public final java.util.Set<androidx.health.services.client.data.ExerciseType> supportedExerciseTypes;
     property public final java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> typeToCapabilities;
   }
 
   public final class ExerciseConfig {
+    ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters, optional androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig, optional java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides);
     ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters, optional androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig);
     ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters);
     method public static androidx.health.services.client.data.ExerciseConfig.Builder builder(androidx.health.services.client.data.ExerciseType exerciseType);
+    method public java.util.Set<androidx.health.services.client.data.BatchingMode> getBatchingModeOverrides();
     method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getDataTypes();
     method public java.util.List<androidx.health.services.client.data.ExerciseGoal<?>> getExerciseGoals();
     method public android.os.Bundle getExerciseParams();
@@ -329,6 +343,7 @@
     method public float getSwimmingPoolLengthMeters();
     method public boolean isAutoPauseAndResumeEnabled();
     method public boolean isGpsEnabled();
+    property public final java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides;
     property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> dataTypes;
     property public final java.util.List<androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals;
     property public final android.os.Bundle exerciseParams;
@@ -344,6 +359,7 @@
   public static final class ExerciseConfig.Builder {
     ctor public ExerciseConfig.Builder(androidx.health.services.client.data.ExerciseType exerciseType);
     method public androidx.health.services.client.data.ExerciseConfig build();
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setBatchingModeOverrides(java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides);
     method public androidx.health.services.client.data.ExerciseConfig.Builder setDataTypes(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes);
     method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseGoals(java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals);
     method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseParams(android.os.Bundle exerciseParams);
diff --git a/health/health-services-client/api/res-1.0.0-beta03.txt b/health/health-services-client/api/res-1.0.0-beta03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/health/health-services-client/api/res-1.0.0-beta03.txt
diff --git a/health/health-services-client/api/restricted_1.0.0-beta03.txt b/health/health-services-client/api/restricted_1.0.0-beta03.txt
new file mode 100644
index 0000000..9d0902d
--- /dev/null
+++ b/health/health-services-client/api/restricted_1.0.0-beta03.txt
@@ -0,0 +1,909 @@
+// Signature format: 4.0
+package androidx.health.services.client {
+
+  @kotlin.jvm.JvmDefaultWithCompatibility public interface ExerciseClient {
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> addGoalToActiveExerciseAsync(androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearUpdateCallbackAsync(androidx.health.services.client.ExerciseUpdateCallback callback);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> endExerciseAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> flushAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseCapabilities> getCapabilitiesAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfoAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLapAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideAutoPauseAndResumeForActiveExerciseAsync(boolean enabled);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideBatchingModesForActiveExerciseAsync(java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModes);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> pauseExerciseAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> prepareExerciseAsync(androidx.health.services.client.data.WarmUpConfig configuration);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> removeGoalFromActiveExerciseAsync(androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> resumeExerciseAsync();
+    method public void setUpdateCallback(androidx.health.services.client.ExerciseUpdateCallback callback);
+    method public void setUpdateCallback(java.util.concurrent.Executor executor, androidx.health.services.client.ExerciseUpdateCallback callback);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> startExerciseAsync(androidx.health.services.client.data.ExerciseConfig configuration);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> updateExerciseTypeConfigAsync(androidx.health.services.client.data.ExerciseTypeConfig exerciseTypeConfig);
+  }
+
+  public final class ExerciseClientExtensionKt {
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? addGoalToActiveExercise(androidx.health.services.client.ExerciseClient, androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? clearUpdateCallback(androidx.health.services.client.ExerciseClient, androidx.health.services.client.ExerciseUpdateCallback callback, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? endExercise(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? flush(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? getCapabilities(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super androidx.health.services.client.data.ExerciseCapabilities>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? getCurrentExerciseInfo(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super androidx.health.services.client.data.ExerciseInfo>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? markLap(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? overrideAutoPauseAndResumeForActiveExercise(androidx.health.services.client.ExerciseClient, boolean enabled, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? pauseExercise(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? prepareExercise(androidx.health.services.client.ExerciseClient, androidx.health.services.client.data.WarmUpConfig configuration, kotlin.coroutines.Continuation<? super kotlin.Unit>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? removeGoalFromActiveExercise(androidx.health.services.client.ExerciseClient, androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? resumeExercise(androidx.health.services.client.ExerciseClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? startExercise(androidx.health.services.client.ExerciseClient, androidx.health.services.client.data.ExerciseConfig configuration, kotlin.coroutines.Continuation<? super kotlin.Unit>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? updateExerciseTypeConfig(androidx.health.services.client.ExerciseClient, androidx.health.services.client.data.ExerciseTypeConfig exerciseTypeConfig, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+  }
+
+  public interface ExerciseUpdateCallback {
+    method public void onAvailabilityChanged(androidx.health.services.client.data.DataType<?,?> dataType, androidx.health.services.client.data.Availability availability);
+    method public void onExerciseUpdateReceived(androidx.health.services.client.data.ExerciseUpdate update);
+    method public void onLapSummaryReceived(androidx.health.services.client.data.ExerciseLapSummary lapSummary);
+    method public void onRegistered();
+    method public void onRegistrationFailed(Throwable throwable);
+  }
+
+  public final class HealthServices {
+    method public static androidx.health.services.client.HealthServicesClient getClient(android.content.Context context);
+    field public static final androidx.health.services.client.HealthServices INSTANCE;
+  }
+
+  public interface HealthServicesClient {
+    method public androidx.health.services.client.ExerciseClient getExerciseClient();
+    method public androidx.health.services.client.MeasureClient getMeasureClient();
+    method public androidx.health.services.client.PassiveMonitoringClient getPassiveMonitoringClient();
+    property public abstract androidx.health.services.client.ExerciseClient exerciseClient;
+    property public abstract androidx.health.services.client.MeasureClient measureClient;
+    property public abstract androidx.health.services.client.PassiveMonitoringClient passiveMonitoringClient;
+  }
+
+  public final class HealthServicesException extends java.lang.Exception {
+    ctor public HealthServicesException(String message);
+  }
+
+  public final class ListenableFutureExtensionKt {
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend <T> Object? awaitWithException(com.google.common.util.concurrent.ListenableFuture<T>, kotlin.coroutines.Continuation<? super T>) throws androidx.health.services.client.HealthServicesException;
+  }
+
+  @kotlin.jvm.JvmDefaultWithCompatibility public interface MeasureCallback {
+    method public void onAvailabilityChanged(androidx.health.services.client.data.DeltaDataType<?,?> dataType, androidx.health.services.client.data.Availability availability);
+    method public void onDataReceived(androidx.health.services.client.data.DataPointContainer data);
+    method public default void onRegistered();
+    method public default void onRegistrationFailed(Throwable throwable);
+  }
+
+  public interface MeasureClient {
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.MeasureCapabilities> getCapabilitiesAsync();
+    method public void registerMeasureCallback(androidx.health.services.client.data.DeltaDataType<?,?> dataType, androidx.health.services.client.MeasureCallback callback);
+    method public void registerMeasureCallback(androidx.health.services.client.data.DeltaDataType<?,?> dataType, java.util.concurrent.Executor executor, androidx.health.services.client.MeasureCallback callback);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> unregisterMeasureCallbackAsync(androidx.health.services.client.data.DeltaDataType<?,?> dataType, androidx.health.services.client.MeasureCallback callback);
+  }
+
+  public final class MeasureClientExtensionKt {
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? getCapabilities(androidx.health.services.client.MeasureClient, kotlin.coroutines.Continuation<? super androidx.health.services.client.data.MeasureCapabilities>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? unregisterMeasureCallback(androidx.health.services.client.MeasureClient, androidx.health.services.client.data.DeltaDataType<?,?> dataType, androidx.health.services.client.MeasureCallback callback, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+  }
+
+  @kotlin.jvm.JvmDefaultWithCompatibility public interface PassiveListenerCallback {
+    method public default void onGoalCompleted(androidx.health.services.client.data.PassiveGoal goal);
+    method public default void onHealthEventReceived(androidx.health.services.client.data.HealthEvent event);
+    method public default void onNewDataPointsReceived(androidx.health.services.client.data.DataPointContainer dataPoints);
+    method public default void onPermissionLost();
+    method public default void onRegistered();
+    method public default void onRegistrationFailed(Throwable throwable);
+    method public default void onUserActivityInfoReceived(androidx.health.services.client.data.UserActivityInfo info);
+  }
+
+  public abstract class PassiveListenerService extends android.app.Service {
+    ctor public PassiveListenerService();
+    method public final android.os.IBinder? onBind(android.content.Intent intent);
+    method public void onGoalCompleted(androidx.health.services.client.data.PassiveGoal goal);
+    method public void onHealthEventReceived(androidx.health.services.client.data.HealthEvent event);
+    method public void onNewDataPointsReceived(androidx.health.services.client.data.DataPointContainer dataPoints);
+    method public void onPermissionLost();
+    method public void onUserActivityInfoReceived(androidx.health.services.client.data.UserActivityInfo info);
+  }
+
+  public interface PassiveMonitoringClient {
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearPassiveListenerCallbackAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearPassiveListenerServiceAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> flushAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.PassiveMonitoringCapabilities> getCapabilitiesAsync();
+    method public void setPassiveListenerCallback(androidx.health.services.client.data.PassiveListenerConfig config, androidx.health.services.client.PassiveListenerCallback callback);
+    method public void setPassiveListenerCallback(androidx.health.services.client.data.PassiveListenerConfig config, java.util.concurrent.Executor executor, androidx.health.services.client.PassiveListenerCallback callback);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> setPassiveListenerServiceAsync(Class<? extends androidx.health.services.client.PassiveListenerService> service, androidx.health.services.client.data.PassiveListenerConfig config);
+  }
+
+  public final class PassiveMonitoringClientExtensionKt {
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? clearPassiveListenerCallback(androidx.health.services.client.PassiveMonitoringClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? clearPassiveListenerService(androidx.health.services.client.PassiveMonitoringClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? flush(androidx.health.services.client.PassiveMonitoringClient, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? getCapabilities(androidx.health.services.client.PassiveMonitoringClient, kotlin.coroutines.Continuation<? super androidx.health.services.client.data.PassiveMonitoringCapabilities>) throws androidx.health.services.client.HealthServicesException;
+    method @kotlin.jvm.Throws(exceptionClasses=HealthServicesException::class) public static suspend Object? setPassiveListenerService(androidx.health.services.client.PassiveMonitoringClient, Class<? extends androidx.health.services.client.PassiveListenerService> service, androidx.health.services.client.data.PassiveListenerConfig config, kotlin.coroutines.Continuation<? super java.lang.Void>) throws androidx.health.services.client.HealthServicesException;
+  }
+
+}
+
+package androidx.health.services.client.data {
+
+  public final class AggregateDataType<T extends java.lang.Number, D extends androidx.health.services.client.data.DataPoint<T>> extends androidx.health.services.client.data.DataType<T,D> {
+    ctor public AggregateDataType(String name, androidx.health.services.client.data.DataType.TimeType timeType, Class<T> valueClass);
+  }
+
+  @kotlin.jvm.JvmDefaultWithCompatibility public interface Availability {
+    method public int getId();
+    property public abstract int id;
+    field public static final androidx.health.services.client.data.Availability.Companion Companion;
+  }
+
+  public static final class Availability.Companion {
+  }
+
+  public final class BatchingMode {
+    field public static final androidx.health.services.client.data.BatchingMode.Companion Companion;
+    field public static final androidx.health.services.client.data.BatchingMode HEART_RATE_5_SECONDS;
+  }
+
+  public static final class BatchingMode.Companion {
+  }
+
+  public final class ComparisonType {
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.ComparisonType.Companion Companion;
+    field public static final androidx.health.services.client.data.ComparisonType GREATER_THAN;
+    field public static final androidx.health.services.client.data.ComparisonType GREATER_THAN_OR_EQUAL;
+    field public static final androidx.health.services.client.data.ComparisonType LESS_THAN;
+    field public static final androidx.health.services.client.data.ComparisonType LESS_THAN_OR_EQUAL;
+    field public static final androidx.health.services.client.data.ComparisonType UNKNOWN;
+  }
+
+  public static final class ComparisonType.Companion {
+  }
+
+  public final class CumulativeDataPoint<T extends java.lang.Number> extends androidx.health.services.client.data.DataPoint<T> {
+    ctor public CumulativeDataPoint(androidx.health.services.client.data.AggregateDataType<T,androidx.health.services.client.data.CumulativeDataPoint<T>> dataType, T total, java.time.Instant start, java.time.Instant end);
+    method public java.time.Instant getEnd();
+    method public java.time.Instant getStart();
+    method public T getTotal();
+    property public final java.time.Instant end;
+    property public final java.time.Instant start;
+    property public final T total;
+  }
+
+  public abstract class DataPoint<T> {
+    method public androidx.health.services.client.data.DataType<T,? extends androidx.health.services.client.data.DataPoint<T>> getDataType();
+    property public androidx.health.services.client.data.DataType<T,? extends androidx.health.services.client.data.DataPoint<T>> dataType;
+  }
+
+  public abstract class DataPointAccuracy {
+    ctor public DataPointAccuracy();
+  }
+
+  public final class DataPointContainer {
+    ctor public DataPointContainer(java.util.Map<androidx.health.services.client.data.DataType<?,?>,? extends java.util.List<? extends androidx.health.services.client.data.DataPoint<?>>> dataPoints);
+    ctor public DataPointContainer(java.util.List<? extends androidx.health.services.client.data.DataPoint<?>> dataPointList);
+    method public java.util.List<androidx.health.services.client.data.CumulativeDataPoint<?>> getCumulativeDataPoints();
+    method public <T, D extends androidx.health.services.client.data.DataPoint<T>> java.util.List<D> getData(androidx.health.services.client.data.DeltaDataType<T,D> type);
+    method public <T extends java.lang.Number, D extends androidx.health.services.client.data.DataPoint<T>> D? getData(androidx.health.services.client.data.AggregateDataType<T,D> type);
+    method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getDataTypes();
+    method public java.util.List<androidx.health.services.client.data.IntervalDataPoint<?>> getIntervalDataPoints();
+    method public java.util.List<androidx.health.services.client.data.SampleDataPoint<?>> getSampleDataPoints();
+    method public java.util.List<androidx.health.services.client.data.StatisticalDataPoint<?>> getStatisticalDataPoints();
+    property public final java.util.List<androidx.health.services.client.data.CumulativeDataPoint<?>> cumulativeDataPoints;
+    property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> dataTypes;
+    property public final java.util.List<androidx.health.services.client.data.IntervalDataPoint<?>> intervalDataPoints;
+    property public final java.util.List<androidx.health.services.client.data.SampleDataPoint<?>> sampleDataPoints;
+    property public final java.util.List<androidx.health.services.client.data.StatisticalDataPoint<?>> statisticalDataPoints;
+  }
+
+  public abstract class DataType<T, D extends androidx.health.services.client.data.DataPoint<T>> {
+    ctor public DataType(String name, androidx.health.services.client.data.DataType.TimeType timeType, Class<T> valueClass, boolean isAggregate);
+    method public final String getName();
+    method public final Class<T> getValueClass();
+    property public final String name;
+    property public final Class<T> valueClass;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> ABSOLUTE_ELEVATION;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> ABSOLUTE_ELEVATION_STATS;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> ACTIVE_EXERCISE_DURATION_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> CALORIES;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> CALORIES_DAILY;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> CALORIES_TOTAL;
+    field public static final androidx.health.services.client.data.DataType.Companion Companion;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> DECLINE_DISTANCE;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> DECLINE_DISTANCE_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> DECLINE_DURATION;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> DECLINE_DURATION_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> DISTANCE;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> DISTANCE_DAILY;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> DISTANCE_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> ELEVATION_GAIN;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> ELEVATION_GAIN_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> ELEVATION_LOSS;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> ELEVATION_LOSS_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> FLAT_GROUND_DISTANCE;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> FLAT_GROUND_DISTANCE_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> FLAT_GROUND_DURATION;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> FLAT_GROUND_DURATION_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> FLOORS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> FLOORS_DAILY;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> FLOORS_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> GOLF_SHOT_COUNT;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> GOLF_SHOT_COUNT_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> HEART_RATE_BPM;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> HEART_RATE_BPM_STATS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.IntervalDataPoint<java.lang.Double>> INCLINE_DISTANCE;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Double>> INCLINE_DISTANCE_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> INCLINE_DURATION;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> INCLINE_DURATION_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<androidx.health.services.client.data.LocationData,androidx.health.services.client.data.SampleDataPoint<androidx.health.services.client.data.LocationData>> LOCATION;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> PACE;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> PACE_STATS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> REP_COUNT;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> REP_COUNT_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> RESTING_EXERCISE_DURATION;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> RESTING_EXERCISE_DURATION_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> RUNNING_STEPS;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> RUNNING_STEPS_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> SPEED;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> SPEED_STATS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> STEPS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> STEPS_DAILY;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.SampleDataPoint<java.lang.Long>> STEPS_PER_MINUTE;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Long>> STEPS_PER_MINUTE_STATS;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> STEPS_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> SWIMMING_LAP_COUNT;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> SWIMMING_STROKES;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> SWIMMING_STROKES_TOTAL;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Double,androidx.health.services.client.data.SampleDataPoint<java.lang.Double>> VO2_MAX;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Double,androidx.health.services.client.data.StatisticalDataPoint<java.lang.Double>> VO2_MAX_STATS;
+    field public static final androidx.health.services.client.data.DeltaDataType<java.lang.Long,androidx.health.services.client.data.IntervalDataPoint<java.lang.Long>> WALKING_STEPS;
+    field public static final androidx.health.services.client.data.AggregateDataType<java.lang.Long,androidx.health.services.client.data.CumulativeDataPoint<java.lang.Long>> WALKING_STEPS_TOTAL;
+  }
+
+  public static final class DataType.Companion {
+  }
+
+  public static final class DataType.TimeType {
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.DataType.TimeType.Companion Companion;
+    field public static final androidx.health.services.client.data.DataType.TimeType INTERVAL;
+    field public static final androidx.health.services.client.data.DataType.TimeType SAMPLE;
+    field public static final androidx.health.services.client.data.DataType.TimeType UNKNOWN;
+  }
+
+  public static final class DataType.TimeType.Companion {
+  }
+
+  public final class DataTypeAvailability implements androidx.health.services.client.data.Availability {
+    method public static androidx.health.services.client.data.DataTypeAvailability? fromId(int id);
+    method public int getId();
+    method public String getName();
+    property public int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.DataTypeAvailability ACQUIRING;
+    field public static final androidx.health.services.client.data.DataTypeAvailability AVAILABLE;
+    field public static final androidx.health.services.client.data.DataTypeAvailability.Companion Companion;
+    field public static final androidx.health.services.client.data.DataTypeAvailability UNAVAILABLE;
+    field public static final androidx.health.services.client.data.DataTypeAvailability UNAVAILABLE_DEVICE_OFF_BODY;
+    field public static final androidx.health.services.client.data.DataTypeAvailability UNKNOWN;
+  }
+
+  public static final class DataTypeAvailability.Companion {
+    method public androidx.health.services.client.data.DataTypeAvailability? fromId(int id);
+  }
+
+  public final class DataTypeCondition<T extends java.lang.Number, D extends androidx.health.services.client.data.DataType<T, ? extends androidx.health.services.client.data.DataPoint<T>>> {
+    ctor public DataTypeCondition(D dataType, T threshold, androidx.health.services.client.data.ComparisonType comparisonType);
+    method public androidx.health.services.client.data.ComparisonType getComparisonType();
+    method public D getDataType();
+    method public T getThreshold();
+    property public final androidx.health.services.client.data.ComparisonType comparisonType;
+    property public final D dataType;
+    property public final T threshold;
+  }
+
+  public final class DeltaDataType<T, D extends androidx.health.services.client.data.DataPoint<T>> extends androidx.health.services.client.data.DataType<T,D> {
+    ctor public DeltaDataType(String name, androidx.health.services.client.data.DataType.TimeType timeType, Class<T> valueClass);
+  }
+
+  public final class ExerciseCapabilities {
+    ctor public ExerciseCapabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> typeToCapabilities, optional java.util.Set<androidx.health.services.client.data.BatchingMode> supportedBatchingModeOverrides);
+    ctor public ExerciseCapabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> typeToCapabilities);
+    method public java.util.Set<androidx.health.services.client.data.ExerciseType> getAutoPauseAndResumeEnabledExercises();
+    method public androidx.health.services.client.data.ExerciseTypeCapabilities getExerciseTypeCapabilities(androidx.health.services.client.data.ExerciseType exercise);
+    method public java.util.Set<androidx.health.services.client.data.BatchingMode> getSupportedBatchingModeOverrides();
+    method public java.util.Set<androidx.health.services.client.data.ExerciseType> getSupportedExerciseTypes();
+    method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> getTypeToCapabilities();
+    property public final java.util.Set<androidx.health.services.client.data.ExerciseType> autoPauseAndResumeEnabledExercises;
+    property public final java.util.Set<androidx.health.services.client.data.BatchingMode> supportedBatchingModeOverrides;
+    property public final java.util.Set<androidx.health.services.client.data.ExerciseType> supportedExerciseTypes;
+    property public final java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> typeToCapabilities;
+  }
+
+  public final class ExerciseConfig {
+    ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters, optional androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig, optional java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides);
+    ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters, optional androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig);
+    ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters);
+    method public static androidx.health.services.client.data.ExerciseConfig.Builder builder(androidx.health.services.client.data.ExerciseType exerciseType);
+    method public java.util.Set<androidx.health.services.client.data.BatchingMode> getBatchingModeOverrides();
+    method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getDataTypes();
+    method public java.util.List<androidx.health.services.client.data.ExerciseGoal<?>> getExerciseGoals();
+    method public android.os.Bundle getExerciseParams();
+    method public androidx.health.services.client.data.ExerciseType getExerciseType();
+    method public androidx.health.services.client.data.ExerciseTypeConfig? getExerciseTypeConfig();
+    method public float getSwimmingPoolLengthMeters();
+    method public boolean isAutoPauseAndResumeEnabled();
+    method public boolean isGpsEnabled();
+    property public final java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides;
+    property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> dataTypes;
+    property public final java.util.List<androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals;
+    property public final android.os.Bundle exerciseParams;
+    property public final androidx.health.services.client.data.ExerciseType exerciseType;
+    property public final androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig;
+    property public final boolean isAutoPauseAndResumeEnabled;
+    property public final boolean isGpsEnabled;
+    property public final float swimmingPoolLengthMeters;
+    field public static final androidx.health.services.client.data.ExerciseConfig.Companion Companion;
+    field public static final float SWIMMING_POOL_LENGTH_UNSPECIFIED = 0.0f;
+  }
+
+  public static final class ExerciseConfig.Builder {
+    ctor public ExerciseConfig.Builder(androidx.health.services.client.data.ExerciseType exerciseType);
+    method public androidx.health.services.client.data.ExerciseConfig build();
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setBatchingModeOverrides(java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setDataTypes(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseGoals(java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseParams(android.os.Bundle exerciseParams);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseTypeConfig(androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setIsAutoPauseAndResumeEnabled(boolean isAutoPauseAndResumeEnabled);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setIsGpsEnabled(boolean isGpsEnabled);
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setSwimmingPoolLengthMeters(float swimmingPoolLength);
+  }
+
+  public static final class ExerciseConfig.Companion {
+    method public androidx.health.services.client.data.ExerciseConfig.Builder builder(androidx.health.services.client.data.ExerciseType exerciseType);
+  }
+
+  public final class ExerciseGoal<T extends java.lang.Number> implements android.os.Parcelable {
+    method public static <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createMilestone(androidx.health.services.client.data.DataTypeCondition<T,androidx.health.services.client.data.AggregateDataType<T,?>> condition, T period);
+    method public static <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal<T> goal, T newThreshold);
+    method public static <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition<T,androidx.health.services.client.data.AggregateDataType<T,?>> condition);
+    method public int describeContents();
+    method public androidx.health.services.client.data.DataTypeCondition<T,androidx.health.services.client.data.AggregateDataType<T,?>> getDataTypeCondition();
+    method public androidx.health.services.client.data.ExerciseGoalType getExerciseGoalType();
+    method public T? getPeriod();
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final androidx.health.services.client.data.DataTypeCondition<T,androidx.health.services.client.data.AggregateDataType<T,?>> dataTypeCondition;
+    property public final androidx.health.services.client.data.ExerciseGoalType exerciseGoalType;
+    property public final T? period;
+    field public static final android.os.Parcelable.Creator<androidx.health.services.client.data.ExerciseGoal<?>> CREATOR;
+    field public static final androidx.health.services.client.data.ExerciseGoal.Companion Companion;
+  }
+
+  public static final class ExerciseGoal.Companion {
+    method public <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createMilestone(androidx.health.services.client.data.DataTypeCondition<T,androidx.health.services.client.data.AggregateDataType<T,?>> condition, T period);
+    method public <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createMilestoneGoalWithUpdatedThreshold(androidx.health.services.client.data.ExerciseGoal<T> goal, T newThreshold);
+    method public <T extends java.lang.Number> androidx.health.services.client.data.ExerciseGoal<T> createOneTimeGoal(androidx.health.services.client.data.DataTypeCondition<T,androidx.health.services.client.data.AggregateDataType<T,?>> condition);
+  }
+
+  public final class ExerciseGoalType {
+    method public static androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.ExerciseGoalType.Companion Companion;
+    field public static final androidx.health.services.client.data.ExerciseGoalType MILESTONE;
+    field public static final androidx.health.services.client.data.ExerciseGoalType ONE_TIME_GOAL;
+  }
+
+  public static final class ExerciseGoalType.Companion {
+    method public androidx.health.services.client.data.ExerciseGoalType? fromId(int id);
+  }
+
+  public final class ExerciseInfo {
+    ctor public ExerciseInfo(int exerciseTrackedStatus, androidx.health.services.client.data.ExerciseType exerciseType);
+    method public int getExerciseTrackedStatus();
+    method public androidx.health.services.client.data.ExerciseType getExerciseType();
+    property public final int exerciseTrackedStatus;
+    property public final androidx.health.services.client.data.ExerciseType exerciseType;
+  }
+
+  public final class ExerciseLapSummary {
+    ctor public ExerciseLapSummary(int lapCount, java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.DataPointContainer lapMetrics);
+    method public java.time.Duration getActiveDuration();
+    method public java.time.Instant getEndTime();
+    method public int getLapCount();
+    method public androidx.health.services.client.data.DataPointContainer getLapMetrics();
+    method public java.time.Instant getStartTime();
+    property public final java.time.Duration activeDuration;
+    property public final java.time.Instant endTime;
+    property public final int lapCount;
+    property public final androidx.health.services.client.data.DataPointContainer lapMetrics;
+    property public final java.time.Instant startTime;
+  }
+
+  public final class ExerciseState {
+    method public static androidx.health.services.client.data.ExerciseState? fromId(int id);
+    method public int getId();
+    method public String getName();
+    method public boolean isEnded();
+    method public boolean isEnding();
+    method public boolean isPaused();
+    method public boolean isResuming();
+    property public final int id;
+    property public final boolean isEnded;
+    property public final boolean isEnding;
+    property public final boolean isPaused;
+    property public final boolean isResuming;
+    property public final String name;
+    field public static final androidx.health.services.client.data.ExerciseState ACTIVE;
+    field public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSED;
+    field public static final androidx.health.services.client.data.ExerciseState AUTO_PAUSING;
+    field public static final androidx.health.services.client.data.ExerciseState AUTO_RESUMING;
+    field public static final androidx.health.services.client.data.ExerciseState.Companion Companion;
+    field public static final androidx.health.services.client.data.ExerciseState ENDED;
+    field public static final androidx.health.services.client.data.ExerciseState ENDING;
+    field public static final androidx.health.services.client.data.ExerciseState PREPARING;
+    field public static final androidx.health.services.client.data.ExerciseState USER_PAUSED;
+    field public static final androidx.health.services.client.data.ExerciseState USER_PAUSING;
+    field public static final androidx.health.services.client.data.ExerciseState USER_RESUMING;
+    field public static final androidx.health.services.client.data.ExerciseState USER_STARTING;
+  }
+
+  public static final class ExerciseState.Companion {
+    method public androidx.health.services.client.data.ExerciseState? fromId(int id);
+  }
+
+  public final class ExerciseStateInfo {
+    ctor public ExerciseStateInfo(androidx.health.services.client.data.ExerciseState exerciseState, int exerciseEndReason);
+    method public int getEndReason();
+    method public androidx.health.services.client.data.ExerciseState getState();
+    property public final int endReason;
+    property public final androidx.health.services.client.data.ExerciseState state;
+    field public static final androidx.health.services.client.data.ExerciseStateInfo.Companion Companion;
+  }
+
+  public static final class ExerciseStateInfo.Companion {
+  }
+
+  public final class ExerciseType {
+    method public static androidx.health.services.client.data.ExerciseType fromId(int id);
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.ExerciseType ALPINE_SKIING;
+    field public static final androidx.health.services.client.data.ExerciseType BACKPACKING;
+    field public static final androidx.health.services.client.data.ExerciseType BACK_EXTENSION;
+    field public static final androidx.health.services.client.data.ExerciseType BADMINTON;
+    field public static final androidx.health.services.client.data.ExerciseType BARBELL_SHOULDER_PRESS;
+    field public static final androidx.health.services.client.data.ExerciseType BASEBALL;
+    field public static final androidx.health.services.client.data.ExerciseType BASKETBALL;
+    field public static final androidx.health.services.client.data.ExerciseType BENCH_PRESS;
+    field public static final androidx.health.services.client.data.ExerciseType BIKING;
+    field public static final androidx.health.services.client.data.ExerciseType BIKING_STATIONARY;
+    field public static final androidx.health.services.client.data.ExerciseType BOOT_CAMP;
+    field public static final androidx.health.services.client.data.ExerciseType BOXING;
+    field public static final androidx.health.services.client.data.ExerciseType BURPEE;
+    field public static final androidx.health.services.client.data.ExerciseType CALISTHENICS;
+    field public static final androidx.health.services.client.data.ExerciseType CRICKET;
+    field public static final androidx.health.services.client.data.ExerciseType CROSS_COUNTRY_SKIING;
+    field public static final androidx.health.services.client.data.ExerciseType CRUNCH;
+    field public static final androidx.health.services.client.data.ExerciseType.Companion Companion;
+    field public static final androidx.health.services.client.data.ExerciseType DANCING;
+    field public static final androidx.health.services.client.data.ExerciseType DEADLIFT;
+    field public static final androidx.health.services.client.data.ExerciseType ELLIPTICAL;
+    field public static final androidx.health.services.client.data.ExerciseType EXERCISE_CLASS;
+    field public static final androidx.health.services.client.data.ExerciseType FENCING;
+    field public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AMERICAN;
+    field public static final androidx.health.services.client.data.ExerciseType FOOTBALL_AUSTRALIAN;
+    field public static final androidx.health.services.client.data.ExerciseType FORWARD_TWIST;
+    field public static final androidx.health.services.client.data.ExerciseType FRISBEE_DISC;
+    field public static final androidx.health.services.client.data.ExerciseType GOLF;
+    field public static final androidx.health.services.client.data.ExerciseType GUIDED_BREATHING;
+    field public static final androidx.health.services.client.data.ExerciseType GYMNASTICS;
+    field public static final androidx.health.services.client.data.ExerciseType HANDBALL;
+    field public static final androidx.health.services.client.data.ExerciseType HIGH_INTENSITY_INTERVAL_TRAINING;
+    field public static final androidx.health.services.client.data.ExerciseType HIKING;
+    field public static final androidx.health.services.client.data.ExerciseType HORSE_RIDING;
+    field public static final androidx.health.services.client.data.ExerciseType ICE_HOCKEY;
+    field public static final androidx.health.services.client.data.ExerciseType ICE_SKATING;
+    field public static final androidx.health.services.client.data.ExerciseType INLINE_SKATING;
+    field public static final androidx.health.services.client.data.ExerciseType JUMPING_JACK;
+    field public static final androidx.health.services.client.data.ExerciseType JUMP_ROPE;
+    field public static final androidx.health.services.client.data.ExerciseType LAT_PULL_DOWN;
+    field public static final androidx.health.services.client.data.ExerciseType LUNGE;
+    field public static final androidx.health.services.client.data.ExerciseType MARTIAL_ARTS;
+    field public static final androidx.health.services.client.data.ExerciseType MEDITATION;
+    field public static final androidx.health.services.client.data.ExerciseType MOUNTAIN_BIKING;
+    field public static final androidx.health.services.client.data.ExerciseType ORIENTEERING;
+    field public static final androidx.health.services.client.data.ExerciseType PADDLING;
+    field public static final androidx.health.services.client.data.ExerciseType PARA_GLIDING;
+    field public static final androidx.health.services.client.data.ExerciseType PILATES;
+    field public static final androidx.health.services.client.data.ExerciseType PLANK;
+    field public static final androidx.health.services.client.data.ExerciseType RACQUETBALL;
+    field public static final androidx.health.services.client.data.ExerciseType ROCK_CLIMBING;
+    field public static final androidx.health.services.client.data.ExerciseType ROLLER_HOCKEY;
+    field public static final androidx.health.services.client.data.ExerciseType ROLLER_SKATING;
+    field public static final androidx.health.services.client.data.ExerciseType ROWING;
+    field public static final androidx.health.services.client.data.ExerciseType ROWING_MACHINE;
+    field public static final androidx.health.services.client.data.ExerciseType RUGBY;
+    field public static final androidx.health.services.client.data.ExerciseType RUNNING;
+    field public static final androidx.health.services.client.data.ExerciseType RUNNING_TREADMILL;
+    field public static final androidx.health.services.client.data.ExerciseType SAILING;
+    field public static final androidx.health.services.client.data.ExerciseType SCUBA_DIVING;
+    field public static final androidx.health.services.client.data.ExerciseType SKATING;
+    field public static final androidx.health.services.client.data.ExerciseType SKIING;
+    field public static final androidx.health.services.client.data.ExerciseType SNOWBOARDING;
+    field public static final androidx.health.services.client.data.ExerciseType SNOWSHOEING;
+    field public static final androidx.health.services.client.data.ExerciseType SOCCER;
+    field public static final androidx.health.services.client.data.ExerciseType SOFTBALL;
+    field public static final androidx.health.services.client.data.ExerciseType SQUASH;
+    field public static final androidx.health.services.client.data.ExerciseType SQUAT;
+    field public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING;
+    field public static final androidx.health.services.client.data.ExerciseType STAIR_CLIMBING_MACHINE;
+    field public static final androidx.health.services.client.data.ExerciseType STRENGTH_TRAINING;
+    field public static final androidx.health.services.client.data.ExerciseType STRETCHING;
+    field public static final androidx.health.services.client.data.ExerciseType SURFING;
+    field public static final androidx.health.services.client.data.ExerciseType SWIMMING_OPEN_WATER;
+    field public static final androidx.health.services.client.data.ExerciseType SWIMMING_POOL;
+    field public static final androidx.health.services.client.data.ExerciseType TABLE_TENNIS;
+    field public static final androidx.health.services.client.data.ExerciseType TENNIS;
+    field public static final androidx.health.services.client.data.ExerciseType UNKNOWN;
+    field public static final androidx.health.services.client.data.ExerciseType UPPER_TWIST;
+    field public static final androidx.health.services.client.data.ExerciseType VOLLEYBALL;
+    field public static final androidx.health.services.client.data.ExerciseType WALKING;
+    field public static final androidx.health.services.client.data.ExerciseType WATER_POLO;
+    field public static final androidx.health.services.client.data.ExerciseType WEIGHTLIFTING;
+    field public static final androidx.health.services.client.data.ExerciseType WORKOUT;
+    field public static final androidx.health.services.client.data.ExerciseType YACHTING;
+    field public static final androidx.health.services.client.data.ExerciseType YOGA;
+  }
+
+  public static final class ExerciseType.Companion {
+    method public androidx.health.services.client.data.ExerciseType fromId(int id);
+  }
+
+  public final class ExerciseTypeCapabilities {
+    ctor public ExerciseTypeCapabilities(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> supportedDataTypes, java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,? extends java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals, java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,? extends java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones, boolean supportsAutoPauseAndResume);
+    method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getSupportedDataTypes();
+    method public java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedGoals();
+    method public java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,java.util.Set<androidx.health.services.client.data.ComparisonType>> getSupportedMilestones();
+    method public boolean getSupportsAutoPauseAndResume();
+    property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> supportedDataTypes;
+    property public final java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedGoals;
+    property public final java.util.Map<androidx.health.services.client.data.AggregateDataType<?,?>,java.util.Set<androidx.health.services.client.data.ComparisonType>> supportedMilestones;
+    property public final boolean supportsAutoPauseAndResume;
+  }
+
+  public abstract class ExerciseTypeConfig {
+    field public static final androidx.health.services.client.data.ExerciseTypeConfig.Companion Companion;
+  }
+
+  public static final class ExerciseTypeConfig.Companion {
+  }
+
+  public final class ExerciseUpdate {
+    method public java.time.Duration getActiveDurationAtDataPoint(androidx.health.services.client.data.IntervalDataPoint<?> dataPoint);
+    method public java.time.Duration getActiveDurationAtDataPoint(androidx.health.services.client.data.SampleDataPoint<?> dataPoint);
+    method public androidx.health.services.client.data.ExerciseUpdate.ActiveDurationCheckpoint? getActiveDurationCheckpoint();
+    method public androidx.health.services.client.data.ExerciseConfig? getExerciseConfig();
+    method public androidx.health.services.client.data.ExerciseStateInfo getExerciseStateInfo();
+    method public java.util.Set<androidx.health.services.client.data.ExerciseGoal<? extends java.lang.Number>> getLatestAchievedGoals();
+    method public androidx.health.services.client.data.DataPointContainer getLatestMetrics();
+    method public java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> getLatestMilestoneMarkerSummaries();
+    method public java.time.Instant? getStartTime();
+    method public java.time.Duration getUpdateDurationFromBoot();
+    property public final androidx.health.services.client.data.ExerciseUpdate.ActiveDurationCheckpoint? activeDurationCheckpoint;
+    property public final androidx.health.services.client.data.ExerciseConfig? exerciseConfig;
+    property public final androidx.health.services.client.data.ExerciseStateInfo exerciseStateInfo;
+    property public final java.util.Set<androidx.health.services.client.data.ExerciseGoal<? extends java.lang.Number>> latestAchievedGoals;
+    property public final androidx.health.services.client.data.DataPointContainer latestMetrics;
+    property public final java.util.Set<androidx.health.services.client.data.MilestoneMarkerSummary> latestMilestoneMarkerSummaries;
+    property public final java.time.Instant? startTime;
+    field public static final androidx.health.services.client.data.ExerciseUpdate.Companion Companion;
+  }
+
+  public static final class ExerciseUpdate.ActiveDurationCheckpoint {
+    ctor public ExerciseUpdate.ActiveDurationCheckpoint(java.time.Instant time, java.time.Duration activeDuration);
+    method public java.time.Duration getActiveDuration();
+    method public java.time.Instant getTime();
+    property public final java.time.Duration activeDuration;
+    property public final java.time.Instant time;
+  }
+
+  public static final class ExerciseUpdate.Companion {
+  }
+
+  public final class GolfExerciseTypeConfig extends androidx.health.services.client.data.ExerciseTypeConfig {
+    ctor public GolfExerciseTypeConfig(optional androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo golfShotTrackingPlaceInfo);
+    method public androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo getGolfShotTrackingPlaceInfo();
+    property public final androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo golfShotTrackingPlaceInfo;
+  }
+
+  public static final class GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo {
+    method public int getPlaceInfoId();
+    property public final int placeInfoId;
+    field public static final androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo.Companion Companion;
+    field public static final androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo GOLF_SHOT_TRACKING_PLACE_INFO_FAIRWAY;
+    field public static final androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo GOLF_SHOT_TRACKING_PLACE_INFO_PUTTING_GREEN;
+    field public static final androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo GOLF_SHOT_TRACKING_PLACE_INFO_TEE_BOX;
+    field public static final androidx.health.services.client.data.GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo GOLF_SHOT_TRACKING_PLACE_INFO_UNSPECIFIED;
+  }
+
+  public static final class GolfExerciseTypeConfig.GolfShotTrackingPlaceInfo.Companion {
+  }
+
+  public final class HealthEvent {
+    ctor public HealthEvent(androidx.health.services.client.data.HealthEvent.Type type, java.time.Instant eventTime, androidx.health.services.client.data.DataPointContainer metrics);
+    method public java.time.Instant getEventTime();
+    method public androidx.health.services.client.data.DataPointContainer getMetrics();
+    method public androidx.health.services.client.data.HealthEvent.Type getType();
+    property public final java.time.Instant eventTime;
+    property public final androidx.health.services.client.data.DataPointContainer metrics;
+    property public final androidx.health.services.client.data.HealthEvent.Type type;
+  }
+
+  public static final class HealthEvent.Type {
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.HealthEvent.Type.Companion Companion;
+    field public static final androidx.health.services.client.data.HealthEvent.Type FALL_DETECTED;
+    field public static final androidx.health.services.client.data.HealthEvent.Type UNKNOWN;
+  }
+
+  public static final class HealthEvent.Type.Companion {
+  }
+
+  public final class HeartRateAccuracy extends androidx.health.services.client.data.DataPointAccuracy {
+    ctor public HeartRateAccuracy(androidx.health.services.client.data.HeartRateAccuracy.SensorStatus sensorStatus);
+    method public androidx.health.services.client.data.HeartRateAccuracy.SensorStatus getSensorStatus();
+    property public final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus sensorStatus;
+  }
+
+  public static final class HeartRateAccuracy.SensorStatus {
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus ACCURACY_HIGH;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus ACCURACY_LOW;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus ACCURACY_MEDIUM;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus.Companion Companion;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus NO_CONTACT;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus UNKNOWN;
+    field public static final androidx.health.services.client.data.HeartRateAccuracy.SensorStatus UNRELIABLE;
+  }
+
+  public static final class HeartRateAccuracy.SensorStatus.Companion {
+  }
+
+  public final class IntervalDataPoint<T> extends androidx.health.services.client.data.DataPoint<T> {
+    ctor public IntervalDataPoint(androidx.health.services.client.data.DataType<T,? extends androidx.health.services.client.data.IntervalDataPoint<T>> dataType, T value, java.time.Duration startDurationFromBoot, java.time.Duration endDurationFromBoot, optional android.os.Bundle metadata, optional androidx.health.services.client.data.DataPointAccuracy? accuracy);
+    method public androidx.health.services.client.data.DataPointAccuracy? getAccuracy();
+    method public androidx.health.services.client.data.DataType<T,? extends androidx.health.services.client.data.IntervalDataPoint<T>> getDataType();
+    method public java.time.Duration getEndDurationFromBoot();
+    method public java.time.Instant getEndInstant(java.time.Instant bootInstant);
+    method public android.os.Bundle getMetadata();
+    method public java.time.Duration getStartDurationFromBoot();
+    method public java.time.Instant getStartInstant(java.time.Instant bootInstant);
+    method public T getValue();
+    property public final androidx.health.services.client.data.DataPointAccuracy? accuracy;
+    property public androidx.health.services.client.data.DataType<T,? extends androidx.health.services.client.data.IntervalDataPoint<T>> dataType;
+    property public final java.time.Duration endDurationFromBoot;
+    property public final android.os.Bundle metadata;
+    property public final java.time.Duration startDurationFromBoot;
+    property public final T value;
+  }
+
+  public final class LocationAccuracy extends androidx.health.services.client.data.DataPointAccuracy {
+    ctor public LocationAccuracy(@FloatRange(from=0.0) double horizontalPositionErrorMeters, optional @FloatRange(from=0.0) double verticalPositionErrorMeters);
+    method public double getHorizontalPositionErrorMeters();
+    method public double getVerticalPositionErrorMeters();
+    property public final double horizontalPositionErrorMeters;
+    property public final double verticalPositionErrorMeters;
+    field public static final androidx.health.services.client.data.LocationAccuracy.Companion Companion;
+  }
+
+  public static final class LocationAccuracy.Companion {
+  }
+
+  public final class LocationAvailability implements androidx.health.services.client.data.Availability {
+    method public static androidx.health.services.client.data.LocationAvailability? fromId(int id);
+    method public int getId();
+    method public String getName();
+    property public int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.LocationAvailability ACQUIRED_TETHERED;
+    field public static final androidx.health.services.client.data.LocationAvailability ACQUIRED_UNTETHERED;
+    field public static final androidx.health.services.client.data.LocationAvailability ACQUIRING;
+    field public static final androidx.health.services.client.data.LocationAvailability.Companion Companion;
+    field public static final androidx.health.services.client.data.LocationAvailability NO_GNSS;
+    field public static final androidx.health.services.client.data.LocationAvailability UNAVAILABLE;
+    field public static final androidx.health.services.client.data.LocationAvailability UNKNOWN;
+  }
+
+  public static final class LocationAvailability.Companion {
+    method public androidx.health.services.client.data.LocationAvailability? fromId(int id);
+  }
+
+  public final class LocationData {
+    ctor public LocationData(@FloatRange(from=-90.0, to=90.0) double latitude, @FloatRange(from=-180.0, to=180.0) double longitude, optional double altitude, optional double bearing);
+    method public double getAltitude();
+    method public double getBearing();
+    method public double getLatitude();
+    method public double getLongitude();
+    property public final double altitude;
+    property public final double bearing;
+    property public final double latitude;
+    property public final double longitude;
+    field public static final double ALTITUDE_UNAVAILABLE = (0.0/0.0);
+    field public static final double BEARING_UNAVAILABLE = (0.0/0.0);
+  }
+
+  public final class MeasureCapabilities {
+    ctor public MeasureCapabilities(java.util.Set<? extends androidx.health.services.client.data.DeltaDataType<?,?>> supportedDataTypesMeasure);
+    method public java.util.Set<androidx.health.services.client.data.DeltaDataType<?,?>> getSupportedDataTypesMeasure();
+    property public final java.util.Set<androidx.health.services.client.data.DeltaDataType<?,?>> supportedDataTypesMeasure;
+  }
+
+  public final class MilestoneMarkerSummary {
+    ctor public MilestoneMarkerSummary(java.time.Instant startTime, java.time.Instant endTime, java.time.Duration activeDuration, androidx.health.services.client.data.ExerciseGoal<? extends java.lang.Number> achievedGoal, androidx.health.services.client.data.DataPointContainer summaryMetrics);
+    method public androidx.health.services.client.data.ExerciseGoal<? extends java.lang.Number> getAchievedGoal();
+    method public java.time.Duration getActiveDuration();
+    method public java.time.Instant getEndTime();
+    method public java.time.Instant getStartTime();
+    method public androidx.health.services.client.data.DataPointContainer getSummaryMetrics();
+    property public final androidx.health.services.client.data.ExerciseGoal<? extends java.lang.Number> achievedGoal;
+    property public final java.time.Duration activeDuration;
+    property public final java.time.Instant endTime;
+    property public final java.time.Instant startTime;
+    property public final androidx.health.services.client.data.DataPointContainer summaryMetrics;
+  }
+
+  public final class PassiveGoal {
+    ctor public PassiveGoal(androidx.health.services.client.data.DataTypeCondition<? extends java.lang.Number,? extends androidx.health.services.client.data.DeltaDataType<? extends java.lang.Number,?>> dataTypeCondition);
+    method public androidx.health.services.client.data.DataTypeCondition<? extends java.lang.Number,? extends androidx.health.services.client.data.DeltaDataType<? extends java.lang.Number,?>> getDataTypeCondition();
+    property public final androidx.health.services.client.data.DataTypeCondition<? extends java.lang.Number,? extends androidx.health.services.client.data.DeltaDataType<? extends java.lang.Number,?>> dataTypeCondition;
+  }
+
+  public final class PassiveListenerConfig {
+    ctor public PassiveListenerConfig(java.util.Set<? extends androidx.health.services.client.data.DataType<? extends java.lang.Object,? extends androidx.health.services.client.data.DataPoint<?>>> dataTypes, boolean shouldUserActivityInfoBeRequested, java.util.Set<androidx.health.services.client.data.PassiveGoal> dailyGoals, java.util.Set<androidx.health.services.client.data.HealthEvent.Type> healthEventTypes);
+    method public static androidx.health.services.client.data.PassiveListenerConfig.Builder builder();
+    method public java.util.Set<androidx.health.services.client.data.PassiveGoal> getDailyGoals();
+    method public java.util.Set<androidx.health.services.client.data.DataType<? extends java.lang.Object,? extends androidx.health.services.client.data.DataPoint<?>>> getDataTypes();
+    method public java.util.Set<androidx.health.services.client.data.HealthEvent.Type> getHealthEventTypes();
+    method public boolean getShouldUserActivityInfoBeRequested();
+    property public final java.util.Set<androidx.health.services.client.data.PassiveGoal> dailyGoals;
+    property public final java.util.Set<androidx.health.services.client.data.DataType<? extends java.lang.Object,? extends androidx.health.services.client.data.DataPoint<?>>> dataTypes;
+    property public final java.util.Set<androidx.health.services.client.data.HealthEvent.Type> healthEventTypes;
+    property public final boolean shouldUserActivityInfoBeRequested;
+    field public static final androidx.health.services.client.data.PassiveListenerConfig.Companion Companion;
+  }
+
+  public static final class PassiveListenerConfig.Builder {
+    ctor public PassiveListenerConfig.Builder();
+    method public androidx.health.services.client.data.PassiveListenerConfig build();
+    method public androidx.health.services.client.data.PassiveListenerConfig.Builder setDailyGoals(java.util.Set<androidx.health.services.client.data.PassiveGoal> dailyGoals);
+    method public androidx.health.services.client.data.PassiveListenerConfig.Builder setDataTypes(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes);
+    method public androidx.health.services.client.data.PassiveListenerConfig.Builder setHealthEventTypes(java.util.Set<androidx.health.services.client.data.HealthEvent.Type> healthEventTypes);
+    method public androidx.health.services.client.data.PassiveListenerConfig.Builder setShouldUserActivityInfoBeRequested(boolean shouldUserActivityInfoBeRequested);
+  }
+
+  public static final class PassiveListenerConfig.Companion {
+    method public androidx.health.services.client.data.PassiveListenerConfig.Builder builder();
+  }
+
+  public final class PassiveMonitoringCapabilities {
+    ctor public PassiveMonitoringCapabilities(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> supportedDataTypesPassiveMonitoring, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> supportedDataTypesPassiveGoals, java.util.Set<androidx.health.services.client.data.HealthEvent.Type> supportedHealthEventTypes, java.util.Set<androidx.health.services.client.data.UserActivityState> supportedUserActivityStates);
+    method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getSupportedDataTypesPassiveGoals();
+    method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getSupportedDataTypesPassiveMonitoring();
+    method public java.util.Set<androidx.health.services.client.data.HealthEvent.Type> getSupportedHealthEventTypes();
+    method public java.util.Set<androidx.health.services.client.data.UserActivityState> getSupportedUserActivityStates();
+    property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> supportedDataTypesPassiveGoals;
+    property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> supportedDataTypesPassiveMonitoring;
+    property public final java.util.Set<androidx.health.services.client.data.HealthEvent.Type> supportedHealthEventTypes;
+    property public final java.util.Set<androidx.health.services.client.data.UserActivityState> supportedUserActivityStates;
+  }
+
+  public final class PassiveMonitoringUpdate {
+    ctor public PassiveMonitoringUpdate(androidx.health.services.client.data.DataPointContainer dataPoints, java.util.List<androidx.health.services.client.data.UserActivityInfo> userActivityInfoUpdates);
+    method public androidx.health.services.client.data.DataPointContainer getDataPoints();
+    method public java.util.List<androidx.health.services.client.data.UserActivityInfo> getUserActivityInfoUpdates();
+    property public final androidx.health.services.client.data.DataPointContainer dataPoints;
+    property public final java.util.List<androidx.health.services.client.data.UserActivityInfo> userActivityInfoUpdates;
+  }
+
+  public final class SampleDataPoint<T> extends androidx.health.services.client.data.DataPoint<T> {
+    ctor public SampleDataPoint(androidx.health.services.client.data.DataType<T,androidx.health.services.client.data.SampleDataPoint<T>> dataType, T value, java.time.Duration timeDurationFromBoot, optional android.os.Bundle metadata, optional androidx.health.services.client.data.DataPointAccuracy? accuracy);
+    method public androidx.health.services.client.data.DataPointAccuracy? getAccuracy();
+    method public androidx.health.services.client.data.DataType<T,androidx.health.services.client.data.SampleDataPoint<T>> getDataType();
+    method public android.os.Bundle getMetadata();
+    method public java.time.Duration getTimeDurationFromBoot();
+    method public java.time.Instant getTimeInstant(java.time.Instant bootInstant);
+    method public T getValue();
+    property public final androidx.health.services.client.data.DataPointAccuracy? accuracy;
+    property public androidx.health.services.client.data.DataType<T,androidx.health.services.client.data.SampleDataPoint<T>> dataType;
+    property public final android.os.Bundle metadata;
+    property public final java.time.Duration timeDurationFromBoot;
+    property public final T value;
+  }
+
+  public final class StatisticalDataPoint<T extends java.lang.Number> extends androidx.health.services.client.data.DataPoint<T> {
+    ctor public StatisticalDataPoint(androidx.health.services.client.data.AggregateDataType<T,androidx.health.services.client.data.StatisticalDataPoint<T>> dataType, T min, T max, T average, java.time.Instant start, java.time.Instant end);
+    method public T getAverage();
+    method public java.time.Instant getEnd();
+    method public T getMax();
+    method public T getMin();
+    method public java.time.Instant getStart();
+    property public final T average;
+    property public final java.time.Instant end;
+    property public final T max;
+    property public final T min;
+    property public final java.time.Instant start;
+    field public static final androidx.health.services.client.data.StatisticalDataPoint.Companion Companion;
+  }
+
+  public static final class StatisticalDataPoint.Companion {
+  }
+
+  public final class UserActivityInfo {
+    ctor public UserActivityInfo(androidx.health.services.client.data.UserActivityState userActivityState, androidx.health.services.client.data.ExerciseInfo? exerciseInfo, java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.UserActivityInfo createActiveExerciseState(androidx.health.services.client.data.ExerciseInfo exerciseInfo, java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.UserActivityInfo createAsleepState(java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.UserActivityInfo createPassiveActivityState(java.time.Instant stateChangeTime);
+    method public static androidx.health.services.client.data.UserActivityInfo createUnknownTypeState(java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.ExerciseInfo? getExerciseInfo();
+    method public java.time.Instant getStateChangeTime();
+    method public androidx.health.services.client.data.UserActivityState getUserActivityState();
+    property public final androidx.health.services.client.data.ExerciseInfo? exerciseInfo;
+    property public final java.time.Instant stateChangeTime;
+    property public final androidx.health.services.client.data.UserActivityState userActivityState;
+    field public static final androidx.health.services.client.data.UserActivityInfo.Companion Companion;
+  }
+
+  public static final class UserActivityInfo.Companion {
+    method public androidx.health.services.client.data.UserActivityInfo createActiveExerciseState(androidx.health.services.client.data.ExerciseInfo exerciseInfo, java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.UserActivityInfo createAsleepState(java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.UserActivityInfo createPassiveActivityState(java.time.Instant stateChangeTime);
+    method public androidx.health.services.client.data.UserActivityInfo createUnknownTypeState(java.time.Instant stateChangeTime);
+  }
+
+  public final class UserActivityState {
+    ctor public UserActivityState(int id, String name);
+    method public int getId();
+    method public String getName();
+    property public final int id;
+    property public final String name;
+    field public static final androidx.health.services.client.data.UserActivityState.Companion Companion;
+    field public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_ASLEEP;
+    field public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_EXERCISE;
+    field public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_PASSIVE;
+    field public static final androidx.health.services.client.data.UserActivityState USER_ACTIVITY_UNKNOWN;
+  }
+
+  public static final class UserActivityState.Companion {
+  }
+
+  public final class WarmUpConfig {
+    ctor public WarmUpConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DeltaDataType<?,?>> dataTypes);
+    method public java.util.Set<androidx.health.services.client.data.DeltaDataType<?,?>> getDataTypes();
+    method public androidx.health.services.client.data.ExerciseType getExerciseType();
+    property public final java.util.Set<androidx.health.services.client.data.DeltaDataType<?,?>> dataTypes;
+    property public final androidx.health.services.client.data.ExerciseType exerciseType;
+  }
+
+}
+
diff --git a/health/health-services-client/api/restricted_current.ignore b/health/health-services-client/api/restricted_current.ignore
index a882151..091f290 100644
--- a/health/health-services-client/api/restricted_current.ignore
+++ b/health/health-services-client/api/restricted_current.ignore
@@ -1,3 +1,3 @@
 // Baseline format: 1.0
-AddedAbstractMethod: androidx.health.services.client.ExerciseClient#updateExerciseTypeConfigAsync(androidx.health.services.client.data.ExerciseTypeConfig):
-    Added method androidx.health.services.client.ExerciseClient.updateExerciseTypeConfigAsync(androidx.health.services.client.data.ExerciseTypeConfig)
+AddedAbstractMethod: androidx.health.services.client.ExerciseClient#overrideBatchingModesForActiveExerciseAsync(java.util.Set<androidx.health.services.client.data.BatchingMode>):
+    Added method androidx.health.services.client.ExerciseClient.overrideBatchingModesForActiveExerciseAsync(java.util.Set<androidx.health.services.client.data.BatchingMode>)
diff --git a/health/health-services-client/api/restricted_current.txt b/health/health-services-client/api/restricted_current.txt
index b985692..9d0902d 100644
--- a/health/health-services-client/api/restricted_current.txt
+++ b/health/health-services-client/api/restricted_current.txt
@@ -10,6 +10,7 @@
     method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfoAsync();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLapAsync();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideAutoPauseAndResumeForActiveExerciseAsync(boolean enabled);
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> overrideBatchingModesForActiveExerciseAsync(java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModes);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> pauseExerciseAsync();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> prepareExerciseAsync(androidx.health.services.client.data.WarmUpConfig configuration);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> removeGoalFromActiveExerciseAsync(androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal);
@@ -141,6 +142,14 @@
   public static final class Availability.Companion {
   }
 
+  public final class BatchingMode {
+    field public static final androidx.health.services.client.data.BatchingMode.Companion Companion;
+    field public static final androidx.health.services.client.data.BatchingMode HEART_RATE_5_SECONDS;
+  }
+
+  public static final class BatchingMode.Companion {
+  }
+
   public final class ComparisonType {
     method public int getId();
     method public String getName();
@@ -307,20 +316,25 @@
   }
 
   public final class ExerciseCapabilities {
+    ctor public ExerciseCapabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> typeToCapabilities, optional java.util.Set<androidx.health.services.client.data.BatchingMode> supportedBatchingModeOverrides);
     ctor public ExerciseCapabilities(java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> typeToCapabilities);
     method public java.util.Set<androidx.health.services.client.data.ExerciseType> getAutoPauseAndResumeEnabledExercises();
     method public androidx.health.services.client.data.ExerciseTypeCapabilities getExerciseTypeCapabilities(androidx.health.services.client.data.ExerciseType exercise);
+    method public java.util.Set<androidx.health.services.client.data.BatchingMode> getSupportedBatchingModeOverrides();
     method public java.util.Set<androidx.health.services.client.data.ExerciseType> getSupportedExerciseTypes();
     method public java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> getTypeToCapabilities();
     property public final java.util.Set<androidx.health.services.client.data.ExerciseType> autoPauseAndResumeEnabledExercises;
+    property public final java.util.Set<androidx.health.services.client.data.BatchingMode> supportedBatchingModeOverrides;
     property public final java.util.Set<androidx.health.services.client.data.ExerciseType> supportedExerciseTypes;
     property public final java.util.Map<androidx.health.services.client.data.ExerciseType,androidx.health.services.client.data.ExerciseTypeCapabilities> typeToCapabilities;
   }
 
   public final class ExerciseConfig {
+    ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters, optional androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig, optional java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides);
     ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters, optional androidx.health.services.client.data.ExerciseTypeConfig? exerciseTypeConfig);
     ctor public ExerciseConfig(androidx.health.services.client.data.ExerciseType exerciseType, java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes, boolean isAutoPauseAndResumeEnabled, boolean isGpsEnabled, optional java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals, optional android.os.Bundle exerciseParams, optional @FloatRange(from=0.0) float swimmingPoolLengthMeters);
     method public static androidx.health.services.client.data.ExerciseConfig.Builder builder(androidx.health.services.client.data.ExerciseType exerciseType);
+    method public java.util.Set<androidx.health.services.client.data.BatchingMode> getBatchingModeOverrides();
     method public java.util.Set<androidx.health.services.client.data.DataType<?,?>> getDataTypes();
     method public java.util.List<androidx.health.services.client.data.ExerciseGoal<?>> getExerciseGoals();
     method public android.os.Bundle getExerciseParams();
@@ -329,6 +343,7 @@
     method public float getSwimmingPoolLengthMeters();
     method public boolean isAutoPauseAndResumeEnabled();
     method public boolean isGpsEnabled();
+    property public final java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides;
     property public final java.util.Set<androidx.health.services.client.data.DataType<?,?>> dataTypes;
     property public final java.util.List<androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals;
     property public final android.os.Bundle exerciseParams;
@@ -344,6 +359,7 @@
   public static final class ExerciseConfig.Builder {
     ctor public ExerciseConfig.Builder(androidx.health.services.client.data.ExerciseType exerciseType);
     method public androidx.health.services.client.data.ExerciseConfig build();
+    method public androidx.health.services.client.data.ExerciseConfig.Builder setBatchingModeOverrides(java.util.Set<androidx.health.services.client.data.BatchingMode> batchingModeOverrides);
     method public androidx.health.services.client.data.ExerciseConfig.Builder setDataTypes(java.util.Set<? extends androidx.health.services.client.data.DataType<?,?>> dataTypes);
     method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseGoals(java.util.List<? extends androidx.health.services.client.data.ExerciseGoal<?>> exerciseGoals);
     method public androidx.health.services.client.data.ExerciseConfig.Builder setExerciseParams(android.os.Bundle exerciseParams);
diff --git a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseApiService.aidl b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseApiService.aidl
index 87f3b3e..b52a78c 100644
--- a/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseApiService.aidl
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/IExerciseApiService.aidl
@@ -20,6 +20,7 @@
 import androidx.health.services.client.impl.internal.IExerciseInfoCallback;
 import androidx.health.services.client.impl.internal.IStatusCallback;
 import androidx.health.services.client.impl.request.AutoPauseAndResumeConfigRequest;
+import androidx.health.services.client.impl.request.BatchingModeConfigRequest;
 import androidx.health.services.client.impl.request.CapabilitiesRequest;
 import androidx.health.services.client.impl.request.FlushRequest;
 import androidx.health.services.client.impl.request.ExerciseGoalRequest;
@@ -31,7 +32,7 @@
 /**
  * Interface to make ipc calls for health services exercise api.
  *
- * The next method added to the interface should use ID: 17
+ * The next method added to the interface should use ID: 18
  * (this id needs to be incremented for each added method)
  *
  * @hide
@@ -42,7 +43,7 @@
      * method is added.
      *
      */
-    const int API_VERSION = 3;
+    const int API_VERSION = 4;
 
     /**
      * Returns version of this AIDL interface.
@@ -122,6 +123,13 @@
     void overrideAutoPauseAndResumeForActiveExercise(in AutoPauseAndResumeConfigRequest request, IStatusCallback statusCallback) = 10;
 
     /**
+     * Sets batching mode for an active exercise.
+     *
+     * <p>Added in API version 4.
+     */
+    void overrideBatchingModesForActiveExercise(in BatchingModeConfigRequest request, IStatusCallback statusCallback) = 17;
+
+    /**
      * Method to get capabilities.
      */
     ExerciseCapabilitiesResponse getCapabilities(in CapabilitiesRequest request) = 11;
diff --git a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BatchingModeConfigRequest.aidl
similarity index 67%
copy from tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
copy to health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BatchingModeConfigRequest.aidl
index ebac64e..a8a7168 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
+++ b/health/health-services-client/src/main/aidl/androidx/health/services/client/impl/request/BatchingModeConfigRequest.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,10 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.tv.material
+package androidx.health.services.client.impl.request;
 
-@RequiresOptIn(
-    "This tv-material API is experimental and likely to change or be removed in the future."
-)
-@Retention(AnnotationRetention.BINARY)
-annotation class ExperimentalTvMaterialApi
\ No newline at end of file
+/** @hide */
+parcelable BatchingModeConfigRequest;
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseClient.kt
index d6e9a1b..e1b58d7 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseClient.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseClient.kt
@@ -16,6 +16,7 @@
 
 package androidx.health.services.client
 
+import androidx.health.services.client.data.BatchingMode
 import androidx.health.services.client.data.DataPoint
 import androidx.health.services.client.data.DataType
 import androidx.health.services.client.data.ExerciseCapabilities
@@ -273,6 +274,18 @@
     ): ListenableFuture<Void>
 
     /**
+     * Sets the batching mode for the current exercise.
+     *
+     * @param batchingModes [BatchingMode] overrides for exercise updates. Passing an empty set will
+     * clear all existing overrides.
+     * @return a [ListenableFuture] that completes once the override has completed. This returned
+     * [ListenableFuture] fails if an exercise is not active for this app.
+     */
+    public fun overrideBatchingModesForActiveExerciseAsync(
+        batchingModes: Set<BatchingMode>
+    ): ListenableFuture<Void>
+
+    /**
      * Returns the [ExerciseCapabilities] of this client for the device.
      *
      * This can be used to determine what [ExerciseType]s and [DataType]s this device supports.
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/BatchingMode.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/BatchingMode.kt
new file mode 100644
index 0000000..66c4f95
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/BatchingMode.kt
@@ -0,0 +1,53 @@
+package androidx.health.services.client.data
+
+import androidx.health.services.client.proto.DataProto
+
+/**
+ * Batching mode during an active exercise when the device is in a non-interactive power state, used
+ * in [ExerciseConfig]. Not applicable when device is in interactive state because exercise updates
+ * will be streaming.
+ */
+public class BatchingMode
+internal constructor(
+    /** Unique identifier for the [BatchingMode], as an `int`. */
+    internal val id: Int,
+) {
+
+    internal constructor(
+        proto: DataProto.BatchingMode
+    ) : this(
+        proto.number,
+    )
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is BatchingMode) return false
+
+        if (id != other.id) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        return id
+    }
+
+    internal fun toProto(): DataProto.BatchingMode =
+        DataProto.BatchingMode.forNumber(id) ?: DataProto.BatchingMode.BATCHING_MODE_UNKNOWN
+
+    public companion object {
+        /**
+         * Batching mode for receiving [DataType.HEART_RATE_BPM] updates with fast frequency.
+         *
+         * Note: This mode will cause significantly increased power consumption compared to the
+         * default batching mode, while still being more power efficient than streaming when in
+         * non-interactive state. The exact power/performance tradeoff of this mode is device
+         * implementation dependent but aims for roughly five second updates.
+         */
+        @JvmField public val HEART_RATE_5_SECONDS: BatchingMode = BatchingMode(1)
+
+        @JvmStatic
+        internal fun fromProto(proto: DataProto.BatchingMode): BatchingMode =
+            BatchingMode(proto.number)
+    }
+}
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt
index d2a684e..88c7df1 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseCapabilities.kt
@@ -29,8 +29,17 @@
      * Mapping for each supported [ExerciseType] to its [ExerciseTypeCapabilities] on this device.
      */
     public val typeToCapabilities: Map<ExerciseType, ExerciseTypeCapabilities>,
+    /** Supported [BatchingMode] overrides on this device. */
+    public val supportedBatchingModeOverrides: Set<BatchingMode> = emptySet(),
 ) {
 
+    constructor(
+        typeToCapabilities: Map<ExerciseType, ExerciseTypeCapabilities>
+    ) : this(
+        typeToCapabilities,
+        emptySet()
+    )
+
     internal constructor(
         proto: DataProto.ExerciseCapabilities
     ) : this(
@@ -39,7 +48,8 @@
             .map { entry ->
                 ExerciseType.fromProto(entry.type) to ExerciseTypeCapabilities(entry.capabilities)
             }
-            .toMap()
+            .toMap(),
+        proto.supportedBatchingModeOverridesList.map { BatchingMode(it) }.toSet(),
     )
 
     internal val proto: DataProto.ExerciseCapabilities =
@@ -54,6 +64,9 @@
                     }
                     .sortedBy { it.type.name } // Ensures equals() works correctly
             )
+            .addAllSupportedBatchingModeOverrides(
+                supportedBatchingModeOverrides.map { it.toProto() }
+            )
             .build()
 
     /** Set of supported [ExerciseType] s on this device. */
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseConfig.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseConfig.kt
index ae694c1..9110a8d 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseConfig.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseConfig.kt
@@ -41,6 +41,7 @@
  * this exercise
  * @property exerciseTypeConfig [ExerciseTypeConfig] containing attributes which may be
  * modified after the exercise has started
+ * @property batchingModeOverrides [BatchingMode] overrides for this exercise
  */
 @Suppress("ParcelCreator")
 class ExerciseConfig(
@@ -51,7 +52,8 @@
     val exerciseGoals: List<ExerciseGoal<*>> = listOf(),
     val exerciseParams: Bundle = Bundle(),
     @FloatRange(from = 0.0) val swimmingPoolLengthMeters: Float = SWIMMING_POOL_LENGTH_UNSPECIFIED,
-    val exerciseTypeConfig: ExerciseTypeConfig? = null
+    val exerciseTypeConfig: ExerciseTypeConfig? = null,
+    val batchingModeOverrides: Set<BatchingMode> = emptySet(),
 ) {
     constructor(
         exerciseType: ExerciseType,
@@ -61,6 +63,27 @@
         exerciseGoals: List<ExerciseGoal<*>> = listOf(),
         exerciseParams: Bundle = Bundle(),
         @FloatRange(from = 0.0) swimmingPoolLengthMeters: Float = SWIMMING_POOL_LENGTH_UNSPECIFIED,
+        exerciseTypeConfig: ExerciseTypeConfig? = null,
+    ) : this(
+        exerciseType,
+        dataTypes,
+        isAutoPauseAndResumeEnabled,
+        isGpsEnabled,
+        exerciseGoals,
+        exerciseParams,
+        swimmingPoolLengthMeters,
+        exerciseTypeConfig,
+        emptySet()
+    )
+
+    constructor(
+        exerciseType: ExerciseType,
+        dataTypes: Set<DataType<*, *>>,
+        isAutoPauseAndResumeEnabled: Boolean,
+        isGpsEnabled: Boolean,
+        exerciseGoals: List<ExerciseGoal<*>> = listOf(),
+        exerciseParams: Bundle = Bundle(),
+        @FloatRange(from = 0.0) swimmingPoolLengthMeters: Float = SWIMMING_POOL_LENGTH_UNSPECIFIED,
     ) : this(
             exerciseType,
             dataTypes,
@@ -89,7 +112,8 @@
         },
         if (proto.hasExerciseTypeConfig()) {
             ExerciseTypeConfig.fromProto(proto.exerciseTypeConfig)
-        } else null
+        } else null,
+        proto.batchingModeOverridesList.map { BatchingMode(it) }.toSet(),
     )
 
     init {
@@ -124,6 +148,7 @@
         private var exerciseParams: Bundle = Bundle.EMPTY
         private var swimmingPoolLength: Float = SWIMMING_POOL_LENGTH_UNSPECIFIED
         private var exerciseTypeConfig: ExerciseTypeConfig? = null
+        private var batchingModeOverrides: Set<BatchingMode> = emptySet()
 
         /**
          * Sets the requested [DataType]s that should be tracked during this exercise. If not
@@ -213,6 +238,16 @@
             return this
         }
 
+        /**
+         * Sets the [BatchingMode] overrides for the ongoing exercise.
+         *
+         * @param batchingModeOverrides [BatchingMode] overrides
+         */
+        fun setBatchingModeOverrides(batchingModeOverrides: Set<BatchingMode>): Builder {
+            this.batchingModeOverrides = batchingModeOverrides
+            return this
+        }
+
         /** Returns the built [ExerciseConfig]. */
         fun build(): ExerciseConfig {
             return ExerciseConfig(
@@ -223,7 +258,8 @@
                 exerciseGoals,
                 exerciseParams,
                 swimmingPoolLength,
-                exerciseTypeConfig
+                exerciseTypeConfig,
+                batchingModeOverrides,
             )
         }
     }
@@ -248,6 +284,7 @@
             .addAllExerciseGoals(exerciseGoals.map { it.proto })
             .setExerciseParams(BundlesUtil.toProto(exerciseParams))
             .setSwimmingPoolLength(swimmingPoolLengthMeters)
+            .addAllBatchingModeOverrides(batchingModeOverrides.map { it.toProto() })
         if (exerciseTypeConfig != null) {
             builder.exerciseTypeConfig = exerciseTypeConfig.toProto()
         }
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt
index 9f92ad7..bfd3d63 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt
@@ -22,6 +22,7 @@
 import androidx.core.content.ContextCompat
 import androidx.health.services.client.ExerciseClient
 import androidx.health.services.client.ExerciseUpdateCallback
+import androidx.health.services.client.data.BatchingMode
 import androidx.health.services.client.data.DataType
 import androidx.health.services.client.data.ExerciseCapabilities
 import androidx.health.services.client.data.ExerciseConfig
@@ -38,6 +39,7 @@
 import androidx.health.services.client.impl.ipc.ClientConfiguration
 import androidx.health.services.client.impl.ipc.internal.ConnectionManager
 import androidx.health.services.client.impl.request.AutoPauseAndResumeConfigRequest
+import androidx.health.services.client.impl.request.BatchingModeConfigRequest
 import androidx.health.services.client.impl.request.CapabilitiesRequest
 import androidx.health.services.client.impl.request.ExerciseGoalRequest
 import androidx.health.services.client.impl.request.FlushRequest
@@ -211,6 +213,20 @@
         )
     }
 
+    override fun overrideBatchingModesForActiveExerciseAsync(
+        batchingModes: Set<BatchingMode>
+    ): ListenableFuture<Void> {
+        return executeWithVersionCheck(
+            { service, resultFuture ->
+                service.overrideBatchingModesForActiveExercise(
+                    BatchingModeConfigRequest(packageName, batchingModes),
+                    StatusCallback(resultFuture)
+                )
+            },
+            /* minApiVersion= */ 4
+        )
+    }
+
     override fun getCapabilitiesAsync(): ListenableFuture<ExerciseCapabilities> =
         Futures.transform(
             execute { service -> service.getCapabilities(CapabilitiesRequest(packageName)) },
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BatchingModeConfigRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BatchingModeConfigRequest.kt
new file mode 100644
index 0000000..6179e7b
--- /dev/null
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BatchingModeConfigRequest.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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.health.services.client.impl.request
+
+import android.os.Parcelable
+import androidx.annotation.RestrictTo
+import androidx.health.services.client.data.BatchingMode
+import androidx.health.services.client.data.ProtoParcelable
+import androidx.health.services.client.proto.RequestsProto
+
+/**
+ * Request for updating batching mode of an exercise.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class BatchingModeConfigRequest(
+    public val packageName: String,
+    public val batchingModeOverrides: Set<BatchingMode>,
+) : ProtoParcelable<RequestsProto.BatchingModeConfigRequest>() {
+
+    override val proto: RequestsProto.BatchingModeConfigRequest =
+        RequestsProto.BatchingModeConfigRequest.newBuilder()
+            .setPackageName(packageName)
+            .addAllBatchingModeOverrides(batchingModeOverrides.map { it.toProto() })
+            .build()
+
+    public companion object {
+        @JvmField
+        public val CREATOR: Parcelable.Creator<BatchingModeConfigRequest> = newCreator {
+            val request = RequestsProto.BatchingModeConfigRequest.parseFrom(it)
+            BatchingModeConfigRequest(
+                request.packageName,
+                request.batchingModeOverridesList.map { BatchingMode(it) }.toSet(),
+            )
+        }
+    }
+}
diff --git a/health/health-services-client/src/main/proto/data.proto b/health/health-services-client/src/main/proto/data.proto
index 65e2287..f754276 100644
--- a/health/health-services-client/src/main/proto/data.proto
+++ b/health/health-services-client/src/main/proto/data.proto
@@ -170,6 +170,13 @@
     reserved 3 to max;  // Next ID
   }
   repeated TypeToCapabilitiesEntry type_to_capabilities = 1;
+  repeated BatchingMode supported_batching_mode_overrides = 2 [packed = true];
+  reserved 3 to max;  // Next ID
+}
+
+enum BatchingMode {
+  BATCHING_MODE_UNKNOWN = 0;
+  BATCHING_MODE_HEART_RATE_5_SECONDS = 1;
   reserved 2 to max;  // Next ID
 }
 
@@ -196,8 +203,9 @@
   optional Bundle exercise_params = 7; // TODO(b/241015676): Deprecate
   optional float swimming_pool_length = 8;
   optional ExerciseTypeConfig exercise_type_config = 10;
+  repeated BatchingMode batching_mode_overrides = 11 [packed = true];
   reserved 9;
-  reserved 11 to max;  // Next ID
+  reserved 12 to max;  // Next ID
 }
 
 message ExerciseInfo {
diff --git a/health/health-services-client/src/main/proto/requests.proto b/health/health-services-client/src/main/proto/requests.proto
index 39e0b7c..0a6d865 100644
--- a/health/health-services-client/src/main/proto/requests.proto
+++ b/health/health-services-client/src/main/proto/requests.proto
@@ -35,6 +35,12 @@
   reserved 2 to max;  // Next ID
 }
 
+message BatchingModeConfigRequest {
+  optional string package_name = 1;
+  repeated BatchingMode batching_mode_overrides = 2 [packed = true];
+  reserved 3 to max;  // Next ID
+}
+
 // Request for capabilities.
 message CapabilitiesRequest {
   optional string package_name = 1;
diff --git a/health/health-services-client/src/test/java/androidx/health/services/client/ExerciseClientTest.kt b/health/health-services-client/src/test/java/androidx/health/services/client/ExerciseClientTest.kt
index c61b936..fb50f7c 100644
--- a/health/health-services-client/src/test/java/androidx/health/services/client/ExerciseClientTest.kt
+++ b/health/health-services-client/src/test/java/androidx/health/services/client/ExerciseClientTest.kt
@@ -47,6 +47,7 @@
 import androidx.health.services.client.impl.ipc.ClientConfiguration
 import androidx.health.services.client.impl.ipc.internal.ConnectionManager
 import androidx.health.services.client.impl.request.AutoPauseAndResumeConfigRequest
+import androidx.health.services.client.impl.request.BatchingModeConfigRequest
 import androidx.health.services.client.impl.request.CapabilitiesRequest
 import androidx.health.services.client.impl.request.ExerciseGoalRequest
 import androidx.health.services.client.impl.request.FlushRequest
@@ -61,6 +62,7 @@
 import com.google.common.collect.ImmutableSet
 import com.google.common.truth.Truth
 import java.util.concurrent.CancellationException
+import kotlin.test.assertFailsWith
 import kotlinx.coroutines.async
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.TestScope
@@ -755,6 +757,17 @@
         Truth.assertThat(service.exerciseConfig?.exerciseTypeConfig).isEqualTo(exerciseTypeConfig)
     }
 
+    @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
+    @Test
+    fun overrideBatchingModesForActiveExercise_notImplementedError() = runTest {
+        var request: BatchingModeConfigRequest?
+        request = null
+        assertFailsWith(
+            exceptionClass = NotImplementedError::class,
+            block = { service.overrideBatchingModesForActiveExercise(request, null) }
+        )
+    }
+
     class FakeExerciseUpdateCallback : ExerciseUpdateCallback {
         val availabilities = mutableMapOf<DataType<*, *>, Availability>()
         val registrationFailureThrowables = mutableListOf<Throwable>()
@@ -917,6 +930,13 @@
             throw NotImplementedError()
         }
 
+        override fun overrideBatchingModesForActiveExercise(
+            batchingModeConfigRequest: BatchingModeConfigRequest?,
+            statuscallback: IStatusCallback?
+        ) {
+            throw NotImplementedError()
+        }
+
         override fun getCapabilities(request: CapabilitiesRequest): ExerciseCapabilitiesResponse {
             if (throwException) {
                 throw RemoteException("Remote Exception")
diff --git a/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseConfigTest.kt b/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseConfigTest.kt
index 1dba2cf..da70783e 100644
--- a/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseConfigTest.kt
+++ b/health/health-services-client/src/test/java/androidx/health/services/client/data/ExerciseConfigTest.kt
@@ -46,7 +46,8 @@
             exerciseTypeConfig = GolfExerciseTypeConfig(
                 GolfExerciseTypeConfig
                     .GolfShotTrackingPlaceInfo.GOLF_SHOT_TRACKING_PLACE_INFO_FAIRWAY
-            )
+            ),
+            batchingModeOverrides = setOf(BatchingMode.HEART_RATE_5_SECONDS),
         ).toProto()
 
         val config = ExerciseConfig(proto)
@@ -67,10 +68,11 @@
             .golfShotTrackingPlaceInfo
         ).isEqualTo(GolfExerciseTypeConfig
             .GolfShotTrackingPlaceInfo.GOLF_SHOT_TRACKING_PLACE_INFO_FAIRWAY)
+        assertThat(config.batchingModeOverrides).containsExactly(BatchingMode.HEART_RATE_5_SECONDS)
     }
 
     @Test
-    fun exerciseTypeConfigNull_protoRoundTrip() {
+    fun protoRoundTrip_emptyExerciseTypeConfigAndBatchingModes() {
         val proto = ExerciseConfig(
             ExerciseType.RUNNING,
             setOf(LOCATION, DISTANCE_TOTAL, HEART_RATE_BPM),
diff --git a/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedExerciseClientTest.kt b/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedExerciseClientTest.kt
index 0743bbb..a0c580b 100644
--- a/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedExerciseClientTest.kt
+++ b/health/health-services-client/src/test/java/androidx/health/services/client/impl/ServiceBackedExerciseClientTest.kt
@@ -37,6 +37,7 @@
 import androidx.health.services.client.impl.internal.IStatusCallback
 import androidx.health.services.client.impl.ipc.internal.ConnectionManager
 import androidx.health.services.client.impl.request.AutoPauseAndResumeConfigRequest
+import androidx.health.services.client.impl.request.BatchingModeConfigRequest
 import androidx.health.services.client.impl.request.CapabilitiesRequest
 import androidx.health.services.client.impl.request.ExerciseGoalRequest
 import androidx.health.services.client.impl.request.FlushRequest
@@ -326,6 +327,13 @@
             throw NotImplementedError()
         }
 
+        override fun overrideBatchingModesForActiveExercise(
+            request: BatchingModeConfigRequest?,
+            statusCallback: IStatusCallback?
+        ) {
+            throw NotImplementedError()
+        }
+
         override fun getCapabilities(request: CapabilitiesRequest?): ExerciseCapabilitiesResponse {
             throw NotImplementedError()
         }
diff --git a/hilt/hilt-navigation-compose/api/current.txt b/hilt/hilt-navigation-compose/api/current.txt
index ff89117..99cdd72 100644
--- a/hilt/hilt-navigation-compose/api/current.txt
+++ b/hilt/hilt-navigation-compose/api/current.txt
@@ -2,7 +2,7 @@
 package androidx.hilt.navigation.compose {
 
   public final class HiltViewModelKt {
-    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM hiltViewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String key);
+    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM hiltViewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key);
   }
 
 }
diff --git a/hilt/hilt-navigation-compose/api/public_plus_experimental_current.txt b/hilt/hilt-navigation-compose/api/public_plus_experimental_current.txt
index ff89117..99cdd72 100644
--- a/hilt/hilt-navigation-compose/api/public_plus_experimental_current.txt
+++ b/hilt/hilt-navigation-compose/api/public_plus_experimental_current.txt
@@ -2,7 +2,7 @@
 package androidx.hilt.navigation.compose {
 
   public final class HiltViewModelKt {
-    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM hiltViewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String key);
+    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM hiltViewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key);
   }
 
 }
diff --git a/hilt/hilt-navigation-compose/api/restricted_current.txt b/hilt/hilt-navigation-compose/api/restricted_current.txt
index 3bf1a26..40d2a82 100644
--- a/hilt/hilt-navigation-compose/api/restricted_current.txt
+++ b/hilt/hilt-navigation-compose/api/restricted_current.txt
@@ -3,7 +3,7 @@
 
   public final class HiltViewModelKt {
     method @androidx.compose.runtime.Composable @kotlin.PublishedApi internal static androidx.lifecycle.ViewModelProvider.Factory? createHiltViewModelFactory(androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner);
-    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM hiltViewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String key);
+    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM hiltViewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key);
   }
 
 }
diff --git a/libraryversions.toml b/libraryversions.toml
index 42c84fa..4e3538a 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -1,18 +1,18 @@
 [versions]
-ACTIVITY = "1.7.0-alpha03"
+ACTIVITY = "1.7.0-alpha04"
 ADS_IDENTIFIER = "1.0.0-alpha05"
-ANNOTATION = "1.6.0-alpha01"
+ANNOTATION = "1.6.0-alpha02"
 ANNOTATION_EXPERIMENTAL = "1.4.0-alpha01"
 APPACTIONS_INTERACTION = "1.0.0-alpha01"
 APPCOMPAT = "1.7.0-alpha02"
 APPSEARCH = "1.1.0-alpha03"
-ARCH_CORE = "2.2.0-alpha01"
+ARCH_CORE = "2.2.0-alpha02"
 ASYNCLAYOUTINFLATER = "1.1.0-alpha02"
 AUTOFILL = "1.2.0-beta02"
-BENCHMARK = "1.2.0-alpha09"
+BENCHMARK = "1.2.0-alpha10"
 BIOMETRIC = "1.2.0-alpha06"
 BLUETOOTH = "1.0.0-alpha01"
-BROWSER = "1.5.0-beta01"
+BROWSER = "1.5.0-beta02"
 BUILDSRC_TESTS = "1.0.0-alpha01"
 CAMERA = "1.3.0-alpha03"
 CAMERA_PIPE = "1.0.0-alpha01"
@@ -20,16 +20,16 @@
 CAR_APP = "1.4.0-alpha01"
 COLLECTION = "1.3.0-alpha03"
 COLLECTION_KMP = "1.3.0-dev01"
-COMPOSE = "1.4.0-alpha04"
-COMPOSE_COMPILER = "1.4.0-alpha02"
-COMPOSE_MATERIAL3 = "1.1.0-alpha04"
+COMPOSE = "1.4.0-alpha05"
+COMPOSE_COMPILER = "1.4.0"
+COMPOSE_MATERIAL3 = "1.1.0-alpha05"
 COMPOSE_RUNTIME_TRACING = "1.0.0-alpha02"
 CONSTRAINTLAYOUT = "2.2.0-alpha06"
 CONSTRAINTLAYOUT_COMPOSE = "1.1.0-alpha06"
 CONSTRAINTLAYOUT_CORE = "1.1.0-alpha06"
 CONTENTPAGER = "1.1.0-alpha01"
 COORDINATORLAYOUT = "1.3.0-alpha01"
-CORE = "1.10.0-alpha01"
+CORE = "1.10.0-alpha02"
 CORE_ANIMATION = "1.0.0-beta02"
 CORE_ANIMATION_TESTING = "1.0.0-beta01"
 CORE_APPDIGEST = "1.0.0-alpha01"
@@ -41,7 +41,7 @@
 CORE_ROLE = "1.2.0-alpha01"
 CORE_SPLASHSCREEN = "1.1.0-alpha01"
 CORE_UWB = "1.0.0-alpha05"
-CREDENTIALS = "1.0.0-alpha01"
+CREDENTIALS = "1.0.0-alpha02"
 CURSORADAPTER = "1.1.0-alpha01"
 CUSTOMVIEW = "1.2.0-alpha03"
 CUSTOMVIEW_POOLINGCONTAINER = "1.1.0-alpha01"
@@ -53,7 +53,7 @@
 DYNAMICANIMATION = "1.1.0-alpha04"
 DYNAMICANIMATION_KTX = "1.0.0-alpha04"
 EMOJI = "1.2.0-alpha03"
-EMOJI2 = "1.3.0-alpha01"
+EMOJI2 = "1.3.0-alpha02"
 ENTERPRISE = "1.1.0-rc01"
 EXIFINTERFACE = "1.4.0-alpha01"
 FRAGMENT = "1.6.0-alpha05"
@@ -63,8 +63,8 @@
 GRAPHICS = "1.0.0-alpha03"
 GRAPHICS_FILTERS = "1.0.0-alpha01"
 GRIDLAYOUT = "1.1.0-alpha01"
-HEALTH_CONNECT = "1.0.0-alpha09"
-HEALTH_SERVICES_CLIENT = "1.0.0-beta02"
+HEALTH_CONNECT = "1.0.0-alpha10"
+HEALTH_SERVICES_CLIENT = "1.0.0-beta03"
 HEIFWRITER = "1.1.0-alpha02"
 HILT = "1.1.0-alpha02"
 HILT_NAVIGATION_COMPOSE = "1.1.0-alpha02"
@@ -79,7 +79,7 @@
 LEANBACK_TAB = "1.1.0-beta01"
 LEGACY = "1.1.0-alpha01"
 LIBYUV = "0.1.0-dev01"
-LIFECYCLE = "2.6.0-alpha04"
+LIFECYCLE = "2.6.0-alpha05"
 LIFECYCLE_EXTENSIONS = "2.2.0"
 LOADER = "1.2.0-alpha01"
 MEDIA = "1.7.0-alpha01"
@@ -96,7 +96,7 @@
 PRIVACYSANDBOX_SDKRUNTIME = "1.0.0-alpha01"
 PRIVACYSANDBOX_TOOLS = "1.0.0-alpha03"
 PRIVACYSANDBOX_UI = "1.0.0-alpha01"
-PROFILEINSTALLER = "1.3.0-alpha03"
+PROFILEINSTALLER = "1.3.0-alpha04"
 RECOMMENDATION = "1.1.0-alpha01"
 RECYCLERVIEW = "1.4.0-alpha01"
 RECYCLERVIEW_SELECTION = "1.2.0-alpha02"
@@ -117,14 +117,14 @@
 SLIDINGPANELAYOUT = "1.3.0-alpha01"
 SQLITE = "2.4.0-alpha01"
 SQLITE_INSPECTOR = "2.1.0-alpha01"
-STARTUP = "1.2.0-alpha02"
+STARTUP = "1.2.0-alpha03"
 SWIPEREFRESHLAYOUT = "1.2.0-alpha01"
 TESTEXT = "1.0.0-alpha01"
 TESTSCREENSHOT = "1.0.0-alpha01"
-TEST_UIAUTOMATOR = "2.3.0-alpha02"
+TEST_UIAUTOMATOR = "2.3.0-alpha03"
 TEXT = "1.0.0-alpha01"
 TRACING = "1.2.0-alpha02"
-TRACING_PERFETTO = "1.0.0-alpha09"
+TRACING_PERFETTO = "1.0.0-alpha10"
 TRANSITION = "1.5.0-alpha01"
 TV = "1.0.0-alpha04"
 TVPROVIDER = "1.1.0-alpha02"
@@ -135,13 +135,13 @@
 VIEWPAGER = "1.1.0-alpha02"
 VIEWPAGER2 = "1.2.0-alpha01"
 WEAR = "1.3.0-alpha04"
-WEAR_COMPOSE = "1.2.0-alpha02"
+WEAR_COMPOSE = "1.2.0-alpha03"
 WEAR_COMPOSE_MATERIAL3 = "1.0.0-alpha01"
 WEAR_INPUT = "1.2.0-alpha03"
 WEAR_INPUT_TESTING = "1.2.0-alpha03"
 WEAR_ONGOING = "1.1.0-alpha01"
 WEAR_PHONE_INTERACTIONS = "1.1.0-alpha04"
-WEAR_PROTOLAYOUT = "1.0.0-alpha01"
+WEAR_PROTOLAYOUT = "1.0.0-alpha02"
 WEAR_REMOTE_INTERACTIONS = "1.1.0-alpha01"
 WEAR_TILES = "1.2.0-alpha01"
 WEAR_WATCHFACE = "1.2.0-alpha06"
diff --git a/lifecycle/lifecycle-livedata-ktx/api/current.ignore b/lifecycle/lifecycle-livedata-ktx/api/current.ignore
new file mode 100644
index 0000000..a088b86
--- /dev/null
+++ b/lifecycle/lifecycle-livedata-ktx/api/current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedClass: androidx.lifecycle.TransformationsKt:
+    Removed class androidx.lifecycle.TransformationsKt
diff --git a/lifecycle/lifecycle-livedata-ktx/api/current.txt b/lifecycle/lifecycle-livedata-ktx/api/current.txt
index 7bf189f..bae0928 100644
--- a/lifecycle/lifecycle-livedata-ktx/api/current.txt
+++ b/lifecycle/lifecycle-livedata-ktx/api/current.txt
@@ -21,11 +21,5 @@
     property public abstract T? latestValue;
   }
 
-  public final class TransformationsKt {
-    method @CheckResult public static inline <X> androidx.lifecycle.LiveData<X> distinctUntilChanged(androidx.lifecycle.LiveData<X>);
-    method @CheckResult public static inline <X, Y> androidx.lifecycle.LiveData<Y> map(androidx.lifecycle.LiveData<X>, kotlin.jvm.functions.Function1<? super X,? extends Y> transform);
-    method @CheckResult public static inline <X, Y> androidx.lifecycle.LiveData<Y> switchMap(androidx.lifecycle.LiveData<X>, kotlin.jvm.functions.Function1<? super X,? extends androidx.lifecycle.LiveData<Y>> transform);
-  }
-
 }
 
diff --git a/lifecycle/lifecycle-livedata-ktx/api/public_plus_experimental_current.txt b/lifecycle/lifecycle-livedata-ktx/api/public_plus_experimental_current.txt
index 7bf189f..bae0928 100644
--- a/lifecycle/lifecycle-livedata-ktx/api/public_plus_experimental_current.txt
+++ b/lifecycle/lifecycle-livedata-ktx/api/public_plus_experimental_current.txt
@@ -21,11 +21,5 @@
     property public abstract T? latestValue;
   }
 
-  public final class TransformationsKt {
-    method @CheckResult public static inline <X> androidx.lifecycle.LiveData<X> distinctUntilChanged(androidx.lifecycle.LiveData<X>);
-    method @CheckResult public static inline <X, Y> androidx.lifecycle.LiveData<Y> map(androidx.lifecycle.LiveData<X>, kotlin.jvm.functions.Function1<? super X,? extends Y> transform);
-    method @CheckResult public static inline <X, Y> androidx.lifecycle.LiveData<Y> switchMap(androidx.lifecycle.LiveData<X>, kotlin.jvm.functions.Function1<? super X,? extends androidx.lifecycle.LiveData<Y>> transform);
-  }
-
 }
 
diff --git a/lifecycle/lifecycle-livedata-ktx/api/restricted_current.ignore b/lifecycle/lifecycle-livedata-ktx/api/restricted_current.ignore
new file mode 100644
index 0000000..a088b86
--- /dev/null
+++ b/lifecycle/lifecycle-livedata-ktx/api/restricted_current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedClass: androidx.lifecycle.TransformationsKt:
+    Removed class androidx.lifecycle.TransformationsKt
diff --git a/lifecycle/lifecycle-livedata-ktx/api/restricted_current.txt b/lifecycle/lifecycle-livedata-ktx/api/restricted_current.txt
index 7bf189f..bae0928 100644
--- a/lifecycle/lifecycle-livedata-ktx/api/restricted_current.txt
+++ b/lifecycle/lifecycle-livedata-ktx/api/restricted_current.txt
@@ -21,11 +21,5 @@
     property public abstract T? latestValue;
   }
 
-  public final class TransformationsKt {
-    method @CheckResult public static inline <X> androidx.lifecycle.LiveData<X> distinctUntilChanged(androidx.lifecycle.LiveData<X>);
-    method @CheckResult public static inline <X, Y> androidx.lifecycle.LiveData<Y> map(androidx.lifecycle.LiveData<X>, kotlin.jvm.functions.Function1<? super X,? extends Y> transform);
-    method @CheckResult public static inline <X, Y> androidx.lifecycle.LiveData<Y> switchMap(androidx.lifecycle.LiveData<X>, kotlin.jvm.functions.Function1<? super X,? extends androidx.lifecycle.LiveData<Y>> transform);
-  }
-
 }
 
diff --git a/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/Transformations.kt b/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/Transformations.kt
deleted file mode 100644
index 3e22243..0000000
--- a/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/Transformations.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright 2018 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.lifecycle
-
-import androidx.annotation.CheckResult
-
-/**
- * Returns a [LiveData] mapped from `this` LiveData by applying [transform] to each value set on
- * `this` LiveData.
- *
- * This method is analogous to [io.reactivex.Observable.map].
- *
- * [transform] will be executed on the main thread.
- *
- * Here is an example mapping a simple `User` struct in a `LiveData` to a
- * `LiveData` containing their full name as a `String`.
- *
- * ```
- * val userLD : LiveData<User> = ...;
- * val userFullNameLD: LiveData<String> = userLD.map { user -> user.firstName + user.lastName }
- * ```
- */
-@CheckResult
-public inline fun <X, Y> LiveData<X>.map(crossinline transform: (X) -> Y): LiveData<Y> =
-    Transformations.map(this) { transform(it) }
-
-/**
- * Returns a [LiveData] mapped from the input `this` `LiveData` by applying
- * [transform] to each value set on `this`.
- * <p>
- * The returned `LiveData` delegates to the most recent `LiveData` created by
- * [transform] with the most recent value set to `this`, without
- * changing the reference. In this way [transform] can change the 'backing'
- * `LiveData` transparently to any observer registered to the `LiveData` returned
- * by `switchMap()`.
- *
- * Note that when the backing `LiveData` is switched, no further values from the older
- * `LiveData` will be set to the output `LiveData`. In this way, the method is
- * analogous to [io.reactivex.Observable.switchMap].
- *
- * [transform] will be executed on the main thread.
- *
- * Here is an example class that holds a typed-in name of a user
- * `String` (such as from an `EditText`) in a [MutableLiveData] and
- * returns a `LiveData` containing a List of `User` objects for users that have
- * that name. It populates that `LiveData` by requerying a repository-pattern object
- * each time the typed name changes.
- * <p>
- * This `ViewModel` would permit the observing UI to update "live" as the user ID text
- * changes.
- *
- * ```
- * class UserViewModel: AndroidViewModel {
- *     val nameQueryLiveData : MutableLiveData<String> = ...
- *
- *     fun usersWithNameLiveData(): LiveData<List<String>> = nameQueryLiveData.switchMap {
- *         name -> myDataSource.usersWithNameLiveData(name)
- *     }
- *
- *     fun setNameQuery(val name: String) {
- *         this.nameQueryLiveData.value = name;
- *     }
- * }
- * ```
- */
-@CheckResult
-public inline fun <X, Y> LiveData<X>.switchMap(
-    crossinline transform: (X) -> LiveData<Y>?
-): LiveData<Y> = Transformations.switchMap(this) { transform(it) }
-
-/**
- * Creates a new [LiveData] object does not emit a value until the source `this` LiveData value
- * has been changed.  The value is considered changed if `equals()` yields `false`.
- */
-@CheckResult
-@Suppress("NOTHING_TO_INLINE")
-public inline fun <X> LiveData<X>.distinctUntilChanged(): LiveData<X> =
-    Transformations.distinctUntilChanged(this)
diff --git a/lifecycle/lifecycle-livedata-ktx/src/test/java/androidx/lifecycle/TransformationsTest.kt b/lifecycle/lifecycle-livedata-ktx/src/test/java/androidx/lifecycle/TransformationsTest.kt
deleted file mode 100644
index e700a4b..0000000
--- a/lifecycle/lifecycle-livedata-ktx/src/test/java/androidx/lifecycle/TransformationsTest.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2018 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.lifecycle
-
-import androidx.arch.core.executor.testing.InstantTaskExecutorRule
-import androidx.lifecycle.testing.TestLifecycleOwner
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import org.junit.Rule
-import org.junit.Test
-
-@Suppress("DEPRECATION")
-class TransformationsTest {
-
-    @get:Rule
-    val mInstantTaskExecutorRule = InstantTaskExecutorRule()
-
-    @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
-    private val lifecycleOwner = TestLifecycleOwner(
-        coroutineDispatcher = UnconfinedTestDispatcher()
-    )
-
-    @Test fun map() {
-        val source = MutableLiveData<String>()
-        val mapped = source.map { input -> input.length }
-        var receivedValue = -1
-        mapped.observe<Int>(lifecycleOwner) { receivedValue = it }
-        source.value = "four"
-        assertThat(receivedValue).isEqualTo(4)
-    }
-
-    @Test fun switchMap() {
-        val trigger = MutableLiveData<Int>()
-        val first = MutableLiveData<String>()
-        val second = MutableLiveData<String>()
-        val result = trigger.switchMap { input -> if (input == 1) first else second }
-
-        var receivedValue = ""
-        result.observe<String>(lifecycleOwner) { receivedValue = it }
-        first.value = "first"
-        trigger.value = 1
-        second.value = "second"
-        assertThat(receivedValue).isEqualTo("first")
-        trigger.value = 2
-        assertThat(receivedValue).isEqualTo("second")
-        first.value = "failure"
-        assertThat(receivedValue).isEqualTo("second")
-    }
-
-    @Test fun distinctUntilChanged() {
-        val originalLiveData = MutableLiveData<String>()
-        val dedupedLiveData = originalLiveData.distinctUntilChanged()
-
-        var counter = 0
-        dedupedLiveData.observe<String>(lifecycleOwner) { counter++ }
-        assertThat(counter).isEqualTo(0)
-
-        originalLiveData.value = "new value"
-        assertThat(dedupedLiveData.value).isEqualTo("new value")
-        assertThat(counter).isEqualTo(1)
-
-        originalLiveData.value = "new value"
-        assertThat(counter).isEqualTo(1)
-
-        originalLiveData.value = "newer value"
-        assertThat(dedupedLiveData.value).isEqualTo("newer value")
-        assertThat(counter).isEqualTo(2)
-    }
-}
diff --git a/lifecycle/lifecycle-livedata/api/current.ignore b/lifecycle/lifecycle-livedata/api/current.ignore
new file mode 100644
index 0000000..f3f2baa
--- /dev/null
+++ b/lifecycle/lifecycle-livedata/api/current.ignore
@@ -0,0 +1,7 @@
+// Baseline format: 1.0
+ChangedType: androidx.lifecycle.Transformations#distinctUntilChanged(androidx.lifecycle.LiveData<X>):
+    Method androidx.lifecycle.Transformations.distinctUntilChanged has changed return type from androidx.lifecycle.LiveData<X!> to androidx.lifecycle.LiveData<X>
+ChangedType: androidx.lifecycle.Transformations#map(androidx.lifecycle.LiveData<X>, androidx.arch.core.util.Function<X,Y>):
+    Method androidx.lifecycle.Transformations.map has changed return type from androidx.lifecycle.LiveData<Y!> to androidx.lifecycle.LiveData<Y>
+ChangedType: androidx.lifecycle.Transformations#switchMap(androidx.lifecycle.LiveData<X>, androidx.arch.core.util.Function<X,androidx.lifecycle.LiveData<Y>>):
+    Method androidx.lifecycle.Transformations.switchMap has changed return type from androidx.lifecycle.LiveData<Y!> to androidx.lifecycle.LiveData<Y>
diff --git a/lifecycle/lifecycle-livedata/api/current.txt b/lifecycle/lifecycle-livedata/api/current.txt
index 2d5345c..9b1bf6c 100644
--- a/lifecycle/lifecycle-livedata/api/current.txt
+++ b/lifecycle/lifecycle-livedata/api/current.txt
@@ -8,10 +8,12 @@
     method @MainThread public <S> void removeSource(androidx.lifecycle.LiveData<S!>);
   }
 
-  public class Transformations {
-    method @MainThread public static <X> androidx.lifecycle.LiveData<X!> distinctUntilChanged(androidx.lifecycle.LiveData<X!>);
-    method @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y!> map(androidx.lifecycle.LiveData<X!>, androidx.arch.core.util.Function<X!,Y!>);
-    method @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y!> switchMap(androidx.lifecycle.LiveData<X!>, androidx.arch.core.util.Function<X!,androidx.lifecycle.LiveData<Y!>!>);
+  public final class Transformations {
+    method @CheckResult @MainThread public static <X> androidx.lifecycle.LiveData<X> distinctUntilChanged(androidx.lifecycle.LiveData<X>);
+    method @CheckResult @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y> map(androidx.lifecycle.LiveData<X>, kotlin.jvm.functions.Function1<X,Y> transform);
+    method @Deprecated @CheckResult @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y> map(androidx.lifecycle.LiveData<X>, androidx.arch.core.util.Function<X,Y> mapFunction);
+    method @CheckResult @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y> switchMap(androidx.lifecycle.LiveData<X>, kotlin.jvm.functions.Function1<X,androidx.lifecycle.LiveData<Y>> transform);
+    method @Deprecated @CheckResult @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y> switchMap(androidx.lifecycle.LiveData<X>, androidx.arch.core.util.Function<X,androidx.lifecycle.LiveData<Y>> switchMapFunction);
   }
 
 }
diff --git a/lifecycle/lifecycle-livedata/api/public_plus_experimental_current.txt b/lifecycle/lifecycle-livedata/api/public_plus_experimental_current.txt
index 2d5345c..9b1bf6c 100644
--- a/lifecycle/lifecycle-livedata/api/public_plus_experimental_current.txt
+++ b/lifecycle/lifecycle-livedata/api/public_plus_experimental_current.txt
@@ -8,10 +8,12 @@
     method @MainThread public <S> void removeSource(androidx.lifecycle.LiveData<S!>);
   }
 
-  public class Transformations {
-    method @MainThread public static <X> androidx.lifecycle.LiveData<X!> distinctUntilChanged(androidx.lifecycle.LiveData<X!>);
-    method @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y!> map(androidx.lifecycle.LiveData<X!>, androidx.arch.core.util.Function<X!,Y!>);
-    method @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y!> switchMap(androidx.lifecycle.LiveData<X!>, androidx.arch.core.util.Function<X!,androidx.lifecycle.LiveData<Y!>!>);
+  public final class Transformations {
+    method @CheckResult @MainThread public static <X> androidx.lifecycle.LiveData<X> distinctUntilChanged(androidx.lifecycle.LiveData<X>);
+    method @CheckResult @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y> map(androidx.lifecycle.LiveData<X>, kotlin.jvm.functions.Function1<X,Y> transform);
+    method @Deprecated @CheckResult @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y> map(androidx.lifecycle.LiveData<X>, androidx.arch.core.util.Function<X,Y> mapFunction);
+    method @CheckResult @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y> switchMap(androidx.lifecycle.LiveData<X>, kotlin.jvm.functions.Function1<X,androidx.lifecycle.LiveData<Y>> transform);
+    method @Deprecated @CheckResult @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y> switchMap(androidx.lifecycle.LiveData<X>, androidx.arch.core.util.Function<X,androidx.lifecycle.LiveData<Y>> switchMapFunction);
   }
 
 }
diff --git a/lifecycle/lifecycle-livedata/api/restricted_current.ignore b/lifecycle/lifecycle-livedata/api/restricted_current.ignore
index e930dd7..43f65eb 100644
--- a/lifecycle/lifecycle-livedata/api/restricted_current.ignore
+++ b/lifecycle/lifecycle-livedata/api/restricted_current.ignore
@@ -1,3 +1,9 @@
 // Baseline format: 1.0
 ChangedType: androidx.lifecycle.ComputableLiveData#getLiveData():
     Method androidx.lifecycle.ComputableLiveData.getLiveData has changed return type from androidx.lifecycle.LiveData<T!> to androidx.lifecycle.LiveData<T>
+ChangedType: androidx.lifecycle.Transformations#distinctUntilChanged(androidx.lifecycle.LiveData<X>):
+    Method androidx.lifecycle.Transformations.distinctUntilChanged has changed return type from androidx.lifecycle.LiveData<X!> to androidx.lifecycle.LiveData<X>
+ChangedType: androidx.lifecycle.Transformations#map(androidx.lifecycle.LiveData<X>, androidx.arch.core.util.Function<X,Y>):
+    Method androidx.lifecycle.Transformations.map has changed return type from androidx.lifecycle.LiveData<Y!> to androidx.lifecycle.LiveData<Y>
+ChangedType: androidx.lifecycle.Transformations#switchMap(androidx.lifecycle.LiveData<X>, androidx.arch.core.util.Function<X,androidx.lifecycle.LiveData<Y>>):
+    Method androidx.lifecycle.Transformations.switchMap has changed return type from androidx.lifecycle.LiveData<Y!> to androidx.lifecycle.LiveData<Y>
diff --git a/lifecycle/lifecycle-livedata/api/restricted_current.txt b/lifecycle/lifecycle-livedata/api/restricted_current.txt
index e25a33f..bb61b39 100644
--- a/lifecycle/lifecycle-livedata/api/restricted_current.txt
+++ b/lifecycle/lifecycle-livedata/api/restricted_current.txt
@@ -17,10 +17,12 @@
     method @MainThread public <S> void removeSource(androidx.lifecycle.LiveData<S!>);
   }
 
-  public class Transformations {
-    method @MainThread public static <X> androidx.lifecycle.LiveData<X!> distinctUntilChanged(androidx.lifecycle.LiveData<X!>);
-    method @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y!> map(androidx.lifecycle.LiveData<X!>, androidx.arch.core.util.Function<X!,Y!>);
-    method @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y!> switchMap(androidx.lifecycle.LiveData<X!>, androidx.arch.core.util.Function<X!,androidx.lifecycle.LiveData<Y!>!>);
+  public final class Transformations {
+    method @CheckResult @MainThread public static <X> androidx.lifecycle.LiveData<X> distinctUntilChanged(androidx.lifecycle.LiveData<X>);
+    method @CheckResult @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y> map(androidx.lifecycle.LiveData<X>, kotlin.jvm.functions.Function1<X,Y> transform);
+    method @Deprecated @CheckResult @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y> map(androidx.lifecycle.LiveData<X>, androidx.arch.core.util.Function<X,Y> mapFunction);
+    method @CheckResult @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y> switchMap(androidx.lifecycle.LiveData<X>, kotlin.jvm.functions.Function1<X,androidx.lifecycle.LiveData<Y>> transform);
+    method @Deprecated @CheckResult @MainThread public static <X, Y> androidx.lifecycle.LiveData<Y> switchMap(androidx.lifecycle.LiveData<X>, androidx.arch.core.util.Function<X,androidx.lifecycle.LiveData<Y>> switchMapFunction);
   }
 
 }
diff --git a/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/Transformations.java b/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/Transformations.java
deleted file mode 100644
index f687e65..0000000
--- a/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/Transformations.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (C) 2017 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.lifecycle;
-
-import androidx.annotation.MainThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.arch.core.util.Function;
-
-/**
- * Transformation methods for {@link LiveData}.
- * <p>
- * These methods permit functional composition and delegation of {@link LiveData} instances. The
- * transformations are calculated lazily, and will run only when the returned {@link LiveData} is
- * observed. Lifecycle behavior is propagated from the input {@code source} {@link LiveData} to the
- * returned one.
- */
-@SuppressWarnings("WeakerAccess")
-public class Transformations {
-
-    private Transformations() {
-    }
-
-    /**
-     * Returns a {@code LiveData} mapped from the input {@code source} {@code LiveData} by applying
-     * {@code mapFunction} to each value set on {@code source}.
-     * <p>
-     * This method is analogous to {@link io.reactivex.Observable#map}.
-     * <p>
-     * {@code transform} will be executed on the main thread.
-     * <p>
-     * Here is an example mapping a simple {@code User} struct in a {@code LiveData} to a
-     * {@code LiveData} containing their full name as a {@code String}.
-     *
-     * <pre>
-     * LiveData&lt;User&gt; userLiveData = ...;
-     * LiveData&lt;String&gt; userFullNameLiveData =
-     *     Transformations.map(
-     *         userLiveData,
-     *         user -> user.firstName + user.lastName);
-     * });
-     * </pre>
-     *
-     * @param source      the {@code LiveData} to map from
-     * @param mapFunction a function to apply to each value set on {@code source} in order to set
-     *                    it
-     *                    on the output {@code LiveData}
-     * @param <X>         the generic type parameter of {@code source}
-     * @param <Y>         the generic type parameter of the returned {@code LiveData}
-     * @return a LiveData mapped from {@code source} to type {@code <Y>} by applying
-     * {@code mapFunction} to each value set.
-     */
-    @MainThread
-    @NonNull
-    public static <X, Y> LiveData<Y> map(
-            @NonNull LiveData<X> source,
-            @NonNull final Function<X, Y> mapFunction) {
-        final MediatorLiveData<Y> result = new MediatorLiveData<>();
-        result.addSource(source, new Observer<X>() {
-            @Override
-            public void onChanged(@Nullable X x) {
-                result.setValue(mapFunction.apply(x));
-            }
-        });
-        return result;
-    }
-
-    /**
-     * Returns a {@code LiveData} mapped from the input {@code source} {@code LiveData} by applying
-     * {@code switchMapFunction} to each value set on {@code source}.
-     * <p>
-     * The returned {@code LiveData} delegates to the most recent {@code LiveData} created by
-     * calling {@code switchMapFunction} with the most recent value set to {@code source}, without
-     * changing the reference. In this way, {@code switchMapFunction} can change the 'backing'
-     * {@code LiveData} transparently to any observer registered to the {@code LiveData} returned
-     * by {@code switchMap()}.
-     * <p>
-     * Note that when the backing {@code LiveData} is switched, no further values from the older
-     * {@code LiveData} will be set to the output {@code LiveData}. In this way, the method is
-     * analogous to {@link io.reactivex.Observable#switchMap}.
-     * <p>
-     * {@code switchMapFunction} will be executed on the main thread.
-     * <p>
-     * Here is an example class that holds a typed-in name of a user
-     * {@code String} (such as from an {@code EditText}) in a {@link MutableLiveData} and
-     * returns a {@code LiveData} containing a List of {@code User} objects for users that have
-     * that name. It populates that {@code LiveData} by requerying a repository-pattern object
-     * each time the typed name changes.
-     * <p>
-     * This {@code ViewModel} would permit the observing UI to update "live" as the user ID text
-     * changes.
-     *
-     * <pre>
-     * class UserViewModel extends AndroidViewModel {
-     *     MutableLiveData&lt;String&gt; nameQueryLiveData = ...
-     *
-     *     LiveData&lt;List&lt;String&gt;&gt; getUsersWithNameLiveData() {
-     *         return Transformations.switchMap(
-     *             nameQueryLiveData,
-     *                 name -> myDataSource.getUsersWithNameLiveData(name));
-     *     }
-     *
-     *     void setNameQuery(String name) {
-     *         this.nameQueryLiveData.setValue(name);
-     *     }
-     * }
-     * </pre>
-     *
-     * @param source            the {@code LiveData} to map from
-     * @param switchMapFunction a function to apply to each value set on {@code source} to create a
-     *                          new delegate {@code LiveData} for the returned one
-     * @param <X>               the generic type parameter of {@code source}
-     * @param <Y>               the generic type parameter of the returned {@code LiveData}
-     * @return a LiveData mapped from {@code source} to type {@code <Y>} by delegating
-     * to the LiveData returned by applying {@code switchMapFunction} to each
-     * value set
-     */
-    @MainThread
-    @NonNull
-    public static <X, Y> LiveData<Y> switchMap(
-            @NonNull LiveData<X> source,
-            @NonNull final Function<X, LiveData<Y>> switchMapFunction) {
-        final MediatorLiveData<Y> result = new MediatorLiveData<>();
-        result.addSource(source, new Observer<X>() {
-            LiveData<Y> mSource;
-
-            @Override
-            public void onChanged(@Nullable X x) {
-                LiveData<Y> newLiveData = switchMapFunction.apply(x);
-                if (mSource == newLiveData) {
-                    return;
-                }
-                if (mSource != null) {
-                    result.removeSource(mSource);
-                }
-                mSource = newLiveData;
-                if (mSource != null) {
-                    result.addSource(mSource, new Observer<Y>() {
-                        @Override
-                        public void onChanged(@Nullable Y y) {
-                            result.setValue(y);
-                        }
-                    });
-                }
-            }
-        });
-        return result;
-    }
-
-    /**
-     * Creates a new {@link LiveData} object that does not emit a value until the source LiveData
-     * value has been changed.  The value is considered changed if {@code equals()} yields
-     * {@code false}.
-     *
-     * @param source the input {@link LiveData}
-     * @param <X>    the generic type parameter of {@code source}
-     * @return       a new {@link LiveData} of type {@code X}
-     */
-    @MainThread
-    @NonNull
-    public static <X> LiveData<X> distinctUntilChanged(@NonNull LiveData<X> source) {
-        final MediatorLiveData<X> outputLiveData = new MediatorLiveData<>();
-        outputLiveData.addSource(source, new Observer<X>() {
-
-            boolean mFirstTime = true;
-
-            @Override
-            public void onChanged(X currentValue) {
-                final X previousValue = outputLiveData.getValue();
-                if (mFirstTime
-                        || (previousValue == null && currentValue != null)
-                        || (previousValue != null && !previousValue.equals(currentValue))) {
-                    mFirstTime = false;
-                    outputLiveData.setValue(currentValue);
-                }
-            }
-        });
-        return outputLiveData;
-    }
-}
diff --git a/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/Transformations.kt b/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/Transformations.kt
new file mode 100644
index 0000000..7c8af62
--- /dev/null
+++ b/lifecycle/lifecycle-livedata/src/main/java/androidx/lifecycle/Transformations.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:JvmName("Transformations")
+
+package androidx.lifecycle
+
+import androidx.annotation.CheckResult
+import androidx.annotation.MainThread
+import androidx.arch.core.util.Function
+
+/**
+ * Returns a [LiveData] mapped from `this` LiveData by applying [transform] to each value set on
+ * `this` LiveData.
+ *
+ * This method is analogous to [io.reactivex.Observable.map].
+ *
+ * [transform] will be executed on the main thread.
+ *
+ * Here is an example mapping a simple `User` struct in a `LiveData` to a
+ * `LiveData` containing their full name as a `String`.
+ *
+ * ```
+ * val userLD : LiveData<User> = ...;
+ * val userFullNameLD: LiveData<String> = userLD.map { user -> user.firstName + user.lastName }
+ * ```
+ *
+ * @param transform a function to apply to each value set on `source` in order to set
+ *                    it on the output `LiveData`
+ * @return a LiveData mapped from `source` to type `<Y>` by applying
+ * `mapFunction` to each value set.
+ */
+@JvmName("map")
+@MainThread
+@CheckResult
+fun <X, Y> LiveData<X>.map(
+    transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards Y)
+): LiveData<Y> {
+    val result = MediatorLiveData<Y>()
+    result.addSource(this) { x -> result.value = transform(x) }
+    return result
+}
+
+@Deprecated(
+    "Use kotlin functions, instead of outdated arch core Functions",
+    level = DeprecationLevel.HIDDEN
+)
+@JvmName("map")
+@MainThread
+@CheckResult
+fun <X, Y> LiveData<X>.map(mapFunction: Function<X, Y>): LiveData<Y> {
+    val result = MediatorLiveData<Y>()
+    result.addSource(this) { x -> result.value = mapFunction.apply(x) }
+    return result
+}
+
+/**
+ * Returns a [LiveData] mapped from the input `this` `LiveData` by applying
+ * [transform] to each value set on `this`.
+ * <p>
+ * The returned `LiveData` delegates to the most recent `LiveData` created by
+ * [transform] with the most recent value set to `this`, without
+ * changing the reference. In this way [transform] can change the 'backing'
+ * `LiveData` transparently to any observer registered to the `LiveData` returned
+ * by `switchMap()`.
+ *
+ * Note that when the backing `LiveData` is switched, no further values from the older
+ * `LiveData` will be set to the output `LiveData`. In this way, the method is
+ * analogous to [io.reactivex.Observable.switchMap].
+ *
+ * [transform] will be executed on the main thread.
+ *
+ * Here is an example class that holds a typed-in name of a user
+ * `String` (such as from an `EditText`) in a [MutableLiveData] and
+ * returns a `LiveData` containing a List of `User` objects for users that have
+ * that name. It populates that `LiveData` by requerying a repository-pattern object
+ * each time the typed name changes.
+ * <p>
+ * This `ViewModel` would permit the observing UI to update "live" as the user ID text
+ * changes.
+ *
+ * ```
+ * class UserViewModel: AndroidViewModel {
+ *     val nameQueryLiveData : MutableLiveData<String> = ...
+ *
+ *     fun usersWithNameLiveData(): LiveData<List<String>> = nameQueryLiveData.switchMap {
+ *         name -> myDataSource.usersWithNameLiveData(name)
+ *     }
+ *
+ *     fun setNameQuery(val name: String) {
+ *         this.nameQueryLiveData.value = name;
+ *     }
+ * }
+ * ```
+ *
+ * @param transform a function to apply to each value set on `source` to create a
+ *                          new delegate `LiveData` for the returned one
+ * @return a LiveData mapped from `source` to type `<Y>` by delegating to the LiveData
+ * returned by applying `switchMapFunction` to each value set
+ */
+@JvmName("switchMap")
+@MainThread
+@CheckResult
+fun <X, Y> LiveData<X>.switchMap(
+    transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards LiveData<Y>)?
+): LiveData<Y> {
+    val result = MediatorLiveData<Y>()
+    result.addSource(this, object : Observer<X> {
+        var liveData: LiveData<Y>? = null
+
+        override fun onChanged(x: X) {
+            val newLiveData = transform(x)
+            if (liveData === newLiveData) {
+                return
+            }
+            if (liveData != null) {
+                result.removeSource(liveData!!)
+            }
+            liveData = newLiveData
+            if (liveData != null) {
+                result.addSource(liveData!!) { y -> result.setValue(y) }
+            }
+        }
+    })
+    return result
+}
+
+@Deprecated(
+    "Use kotlin functions, instead of outdated arch core Functions",
+    level = DeprecationLevel.HIDDEN
+)
+@JvmName("switchMap")
+@MainThread
+@CheckResult
+fun <X, Y> LiveData<X>.switchMap(switchMapFunction: Function<X, LiveData<Y>>): LiveData<Y> {
+    val result = MediatorLiveData<Y>()
+    result.addSource(this, object : Observer<X> {
+        var liveData: LiveData<Y>? = null
+
+        override fun onChanged(x: X) {
+            val newLiveData = switchMapFunction.apply(x)
+            if (liveData === newLiveData) {
+                return
+            }
+            if (liveData != null) {
+                result.removeSource(liveData!!)
+            }
+            liveData = newLiveData
+            if (liveData != null) {
+                result.addSource(liveData!!) { y -> result.setValue(y) }
+            }
+        }
+    })
+    return result
+}
+
+/**
+ * Creates a new [LiveData] object does not emit a value until the source `this` LiveData value
+ * has been changed. The value is considered changed if `equals()` yields `false`.
+ *
+ * @return a new [LiveData] of type `X`
+ */
+@JvmName("distinctUntilChanged")
+@MainThread
+@CheckResult
+fun <X> LiveData<X>.distinctUntilChanged(): LiveData<X> {
+    val outputLiveData = MediatorLiveData<X>()
+    outputLiveData.addSource(this, object : Observer<X> {
+        var firstTime = true
+
+        override fun onChanged(currentValue: X) {
+            val previousValue = outputLiveData.value
+            if (firstTime ||
+                previousValue == null && currentValue != null ||
+                previousValue != null && previousValue != currentValue
+            ) {
+                firstTime = false
+                outputLiveData.value = currentValue
+            }
+        }
+    })
+    return outputLiveData
+}
\ No newline at end of file
diff --git a/lifecycle/lifecycle-livedata/src/test/java/androidx/lifecycle/TransformationsTest.java b/lifecycle/lifecycle-livedata/src/test/java/androidx/lifecycle/TransformationsTest.java
index de44aa0..e77c136 100644
--- a/lifecycle/lifecycle-livedata/src/test/java/androidx/lifecycle/TransformationsTest.java
+++ b/lifecycle/lifecycle-livedata/src/test/java/androidx/lifecycle/TransformationsTest.java
@@ -30,7 +30,6 @@
 
 import androidx.annotation.Nullable;
 import androidx.arch.core.executor.ArchTaskExecutor;
-import androidx.arch.core.util.Function;
 import androidx.lifecycle.testing.TestLifecycleOwner;
 import androidx.lifecycle.util.InstantTaskExecutor;
 
@@ -59,12 +58,7 @@
     @Test
     public void testMap() {
         LiveData<String> source = new MutableLiveData<>();
-        LiveData<Integer> mapped = Transformations.map(source, new Function<String, Integer>() {
-            @Override
-            public Integer apply(String input) {
-                return input.length();
-            }
-        });
+        LiveData<Integer> mapped = Transformations.map(source, String::length);
         Observer<Integer> observer = mock(Observer.class);
         mapped.observe(mOwner, observer);
         source.setValue("four");
@@ -76,15 +70,13 @@
         LiveData<Integer> trigger = new MutableLiveData<>();
         final LiveData<String> first = new MutableLiveData<>();
         final LiveData<String> second = new MutableLiveData<>();
-        LiveData<String> result = Transformations.switchMap(trigger,
-                new Function<Integer, LiveData<String>>() {
-                    @Override
-                    public LiveData<String> apply(Integer input) {
-                        if (input == 1) {
-                            return first;
-                        } else {
-                            return second;
-                        }
+        LiveData<String> result = Transformations.switchMap(
+                trigger,
+                (Integer input) -> {
+                    if (input == 1) {
+                        return first;
+                    } else {
+                        return second;
                     }
                 });
 
@@ -109,15 +101,13 @@
         LiveData<Integer> trigger = new MutableLiveData<>();
         final LiveData<String> first = new MutableLiveData<>();
         final LiveData<String> second = new MutableLiveData<>();
-        LiveData<String> result = Transformations.switchMap(trigger,
-                new Function<Integer, LiveData<String>>() {
-                    @Override
-                    public LiveData<String> apply(Integer input) {
-                        if (input == 1) {
-                            return first;
-                        } else {
-                            return second;
-                        }
+        LiveData<String> result = Transformations.switchMap(
+                trigger,
+                (Integer input) -> {
+                    if (input == 1) {
+                        return first;
+                    } else {
+                        return second;
                     }
                 });
 
@@ -146,13 +136,7 @@
     public void testNoRedispatchSwitchMap() {
         LiveData<Integer> trigger = new MutableLiveData<>();
         final LiveData<String> first = new MutableLiveData<>();
-        LiveData<String> result = Transformations.switchMap(trigger,
-                new Function<Integer, LiveData<String>>() {
-                    @Override
-                    public LiveData<String> apply(Integer input) {
-                        return first;
-                    }
-                });
+        LiveData<String> result = Transformations.switchMap(trigger, (Integer input) -> first);
 
         Observer<String> observer = mock(Observer.class);
         result.observe(mOwner, observer);
@@ -169,15 +153,13 @@
     public void testSwitchMapToNull() {
         LiveData<Integer> trigger = new MutableLiveData<>();
         final LiveData<String> first = new MutableLiveData<>();
-        LiveData<String> result = Transformations.switchMap(trigger,
-                new Function<Integer, LiveData<String>>() {
-                    @Override
-                    public LiveData<String> apply(Integer input) {
-                        if (input == 1) {
-                            return first;
-                        } else {
-                            return null;
-                        }
+        LiveData<String> result = Transformations.switchMap(
+                trigger,
+                (Integer input) -> {
+                    if (input == 1) {
+                        return first;
+                    } else {
+                        return null;
                     }
                 });
 
@@ -197,12 +179,7 @@
     @Test
     public void noObsoleteValueTest() {
         MutableLiveData<Integer> numbers = new MutableLiveData<>();
-        LiveData<Integer> squared = Transformations.map(numbers, new Function<Integer, Integer>() {
-            @Override
-            public Integer apply(Integer input) {
-                return input * input;
-            }
-        });
+        LiveData<Integer> squared = Transformations.map(numbers, (Integer input) -> input * input);
 
         Observer observer = mock(Observer.class);
         squared.setValue(1);
diff --git a/lifecycle/lifecycle-process/api/current.ignore b/lifecycle/lifecycle-process/api/current.ignore
index 56d4ca4..6350fca 100644
--- a/lifecycle/lifecycle-process/api/current.ignore
+++ b/lifecycle/lifecycle-process/api/current.ignore
@@ -1,3 +1,7 @@
 // Baseline format: 1.0
+AddedFinal: androidx.lifecycle.ProcessLifecycleOwner#getLifecycle():
+    Method androidx.lifecycle.ProcessLifecycleOwner.getLifecycle has added 'final' qualifier
+
+
 ChangedType: androidx.lifecycle.ProcessLifecycleInitializer#dependencies():
     Method androidx.lifecycle.ProcessLifecycleInitializer.dependencies has changed return type from java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>>!> to java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>>>
diff --git a/lifecycle/lifecycle-process/api/current.txt b/lifecycle/lifecycle-process/api/current.txt
index 572a7ab..35d5ac4 100644
--- a/lifecycle/lifecycle-process/api/current.txt
+++ b/lifecycle/lifecycle-process/api/current.txt
@@ -7,9 +7,14 @@
     method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>>> dependencies();
   }
 
-  public class ProcessLifecycleOwner implements androidx.lifecycle.LifecycleOwner {
+  public final class ProcessLifecycleOwner implements androidx.lifecycle.LifecycleOwner {
     method public static androidx.lifecycle.LifecycleOwner get();
     method public androidx.lifecycle.Lifecycle getLifecycle();
+    field public static final androidx.lifecycle.ProcessLifecycleOwner.Companion Companion;
+  }
+
+  public static final class ProcessLifecycleOwner.Companion {
+    method public androidx.lifecycle.LifecycleOwner get();
   }
 
 }
diff --git a/lifecycle/lifecycle-process/api/public_plus_experimental_current.txt b/lifecycle/lifecycle-process/api/public_plus_experimental_current.txt
index 572a7ab..35d5ac4 100644
--- a/lifecycle/lifecycle-process/api/public_plus_experimental_current.txt
+++ b/lifecycle/lifecycle-process/api/public_plus_experimental_current.txt
@@ -7,9 +7,14 @@
     method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>>> dependencies();
   }
 
-  public class ProcessLifecycleOwner implements androidx.lifecycle.LifecycleOwner {
+  public final class ProcessLifecycleOwner implements androidx.lifecycle.LifecycleOwner {
     method public static androidx.lifecycle.LifecycleOwner get();
     method public androidx.lifecycle.Lifecycle getLifecycle();
+    field public static final androidx.lifecycle.ProcessLifecycleOwner.Companion Companion;
+  }
+
+  public static final class ProcessLifecycleOwner.Companion {
+    method public androidx.lifecycle.LifecycleOwner get();
   }
 
 }
diff --git a/lifecycle/lifecycle-process/api/restricted_current.ignore b/lifecycle/lifecycle-process/api/restricted_current.ignore
index 56d4ca4..6350fca 100644
--- a/lifecycle/lifecycle-process/api/restricted_current.ignore
+++ b/lifecycle/lifecycle-process/api/restricted_current.ignore
@@ -1,3 +1,7 @@
 // Baseline format: 1.0
+AddedFinal: androidx.lifecycle.ProcessLifecycleOwner#getLifecycle():
+    Method androidx.lifecycle.ProcessLifecycleOwner.getLifecycle has added 'final' qualifier
+
+
 ChangedType: androidx.lifecycle.ProcessLifecycleInitializer#dependencies():
     Method androidx.lifecycle.ProcessLifecycleInitializer.dependencies has changed return type from java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>>!> to java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>>>
diff --git a/lifecycle/lifecycle-process/api/restricted_current.txt b/lifecycle/lifecycle-process/api/restricted_current.txt
index 572a7ab..35d5ac4 100644
--- a/lifecycle/lifecycle-process/api/restricted_current.txt
+++ b/lifecycle/lifecycle-process/api/restricted_current.txt
@@ -7,9 +7,14 @@
     method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>>> dependencies();
   }
 
-  public class ProcessLifecycleOwner implements androidx.lifecycle.LifecycleOwner {
+  public final class ProcessLifecycleOwner implements androidx.lifecycle.LifecycleOwner {
     method public static androidx.lifecycle.LifecycleOwner get();
     method public androidx.lifecycle.Lifecycle getLifecycle();
+    field public static final androidx.lifecycle.ProcessLifecycleOwner.Companion Companion;
+  }
+
+  public static final class ProcessLifecycleOwner.Companion {
+    method public androidx.lifecycle.LifecycleOwner get();
   }
 
 }
diff --git a/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.java b/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.java
deleted file mode 100644
index 0226a4e..0000000
--- a/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (C) 2017 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.lifecycle;
-
-import android.app.Activity;
-import android.app.Application;
-import android.content.Context;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-
-import androidx.annotation.DoNotInline;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.VisibleForTesting;
-import androidx.lifecycle.ReportFragment.ActivityInitializationListener;
-
-/**
- * Class that provides lifecycle for the whole application process.
- * <p>
- * You can consider this LifecycleOwner as the composite of all of your Activities, except that
- * {@link Lifecycle.Event#ON_CREATE} will be dispatched once and {@link Lifecycle.Event#ON_DESTROY}
- * will never be dispatched. Other lifecycle events will be dispatched with following rules:
- * ProcessLifecycleOwner will dispatch {@link Lifecycle.Event#ON_START},
- * {@link Lifecycle.Event#ON_RESUME} events, as a first activity moves through these events.
- * {@link Lifecycle.Event#ON_PAUSE}, {@link Lifecycle.Event#ON_STOP}, events will be dispatched with
- * a <b>delay</b> after a last activity
- * passed through them. This delay is long enough to guarantee that ProcessLifecycleOwner
- * won't send any events if activities are destroyed and recreated due to a
- * configuration change.
- *
- * <p>
- * It is useful for use cases where you would like to react on your app coming to the foreground or
- * going to the background and you don't need a milliseconds accuracy in receiving lifecycle
- * events.
- */
-@SuppressWarnings("WeakerAccess")
-public class ProcessLifecycleOwner implements LifecycleOwner {
-
-    @VisibleForTesting
-    static final long TIMEOUT_MS = 700; //mls
-
-    // ground truth counters
-    private int mStartedCounter = 0;
-    private int mResumedCounter = 0;
-
-    private boolean mPauseSent = true;
-    private boolean mStopSent = true;
-
-    private Handler mHandler;
-    private final LifecycleRegistry mRegistry = new LifecycleRegistry(this);
-
-    private Runnable mDelayedPauseRunnable = new Runnable() {
-        @Override
-        public void run() {
-            dispatchPauseIfNeeded();
-            dispatchStopIfNeeded();
-        }
-    };
-
-    ActivityInitializationListener mInitializationListener =
-            new ActivityInitializationListener() {
-                @Override
-                public void onCreate() {
-                }
-
-                @Override
-                public void onStart() {
-                    activityStarted();
-                }
-
-                @Override
-                public void onResume() {
-                    activityResumed();
-                }
-            };
-
-    private static final ProcessLifecycleOwner sInstance = new ProcessLifecycleOwner();
-
-    /**
-     * The LifecycleOwner for the whole application process. Note that if your application
-     * has multiple processes, this provider does not know about other processes.
-     *
-     * @return {@link LifecycleOwner} for the whole application.
-     */
-    @NonNull
-    public static LifecycleOwner get() {
-        return sInstance;
-    }
-
-    static void init(Context context) {
-        sInstance.attach(context);
-    }
-
-    void activityStarted() {
-        mStartedCounter++;
-        if (mStartedCounter == 1 && mStopSent) {
-            mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-            mStopSent = false;
-        }
-    }
-
-    void activityResumed() {
-        mResumedCounter++;
-        if (mResumedCounter == 1) {
-            if (mPauseSent) {
-                mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
-                mPauseSent = false;
-            } else {
-                mHandler.removeCallbacks(mDelayedPauseRunnable);
-            }
-        }
-    }
-
-    void activityPaused() {
-        mResumedCounter--;
-        if (mResumedCounter == 0) {
-            mHandler.postDelayed(mDelayedPauseRunnable, TIMEOUT_MS);
-        }
-    }
-
-    void activityStopped() {
-        mStartedCounter--;
-        dispatchStopIfNeeded();
-    }
-
-    void dispatchPauseIfNeeded() {
-        if (mResumedCounter == 0) {
-            mPauseSent = true;
-            mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
-        }
-    }
-
-    void dispatchStopIfNeeded() {
-        if (mStartedCounter == 0 && mPauseSent) {
-            mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
-            mStopSent = true;
-        }
-    }
-
-    private ProcessLifecycleOwner() {
-    }
-
-    @SuppressWarnings("deprecation")
-    void attach(Context context) {
-        mHandler = new Handler();
-        mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
-        Application app = (Application) context.getApplicationContext();
-        app.registerActivityLifecycleCallbacks(new EmptyActivityLifecycleCallbacks() {
-            @RequiresApi(29)
-            @Override
-            public void onActivityPreCreated(@NonNull Activity activity,
-                    @Nullable Bundle savedInstanceState) {
-                // We need the ProcessLifecycleOwner to get ON_START and ON_RESUME precisely
-                // before the first activity gets its LifecycleOwner started/resumed.
-                // The activity's LifecycleOwner gets started/resumed via an activity registered
-                // callback added in onCreate(). By adding our own activity registered callback in
-                // onActivityPreCreated(), we get our callbacks first while still having the
-                // right relative order compared to the Activity's onStart()/onResume() callbacks.
-                Api29Impl.registerActivityLifecycleCallbacks(activity,
-                        new EmptyActivityLifecycleCallbacks() {
-                            @Override
-                            public void onActivityPostStarted(@NonNull Activity activity) {
-                                activityStarted();
-                            }
-
-                            @Override
-                            public void onActivityPostResumed(@NonNull Activity activity) {
-                                activityResumed();
-                            }
-                        });
-            }
-
-            @Override
-            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
-                // Only use ReportFragment pre API 29 - after that, we can use the
-                // onActivityPostStarted and onActivityPostResumed callbacks registered in
-                // onActivityPreCreated()
-                if (Build.VERSION.SDK_INT < 29) {
-                    ReportFragment.get(activity).setProcessListener(mInitializationListener);
-                }
-            }
-
-            @Override
-            public void onActivityPaused(Activity activity) {
-                activityPaused();
-            }
-
-            @Override
-            public void onActivityStopped(Activity activity) {
-                activityStopped();
-            }
-        });
-    }
-
-    @NonNull
-    @Override
-    public Lifecycle getLifecycle() {
-        return mRegistry;
-    }
-
-    @RequiresApi(29)
-    static class Api29Impl {
-        private Api29Impl() {
-            // This class is not instantiable.
-        }
-
-        @DoNotInline
-        static void registerActivityLifecycleCallbacks(@NonNull Activity activity,
-                @NonNull Application.ActivityLifecycleCallbacks callback) {
-            activity.registerActivityLifecycleCallbacks(callback);
-        }
-    }
-}
diff --git a/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.kt b/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.kt
new file mode 100644
index 0000000..657b9e0
--- /dev/null
+++ b/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 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.lifecycle
+
+import android.app.Activity
+import android.app.Application
+import android.content.Context
+import android.os.Build
+import android.os.Bundle
+import android.os.Handler
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+import androidx.annotation.VisibleForTesting
+
+/**
+ * Class that provides lifecycle for the whole application process.
+ *
+ * You can consider this LifecycleOwner as the composite of all of your Activities, except that
+ * [Lifecycle.Event.ON_CREATE] will be dispatched once and [Lifecycle.Event.ON_DESTROY]
+ * will never be dispatched. Other lifecycle events will be dispatched with following rules:
+ * ProcessLifecycleOwner will dispatch [Lifecycle.Event.ON_START],
+ * [Lifecycle.Event.ON_RESUME] events, as a first activity moves through these events.
+ * [Lifecycle.Event.ON_PAUSE], [Lifecycle.Event.ON_STOP], events will be dispatched with
+ * a **delay** after a last activity
+ * passed through them. This delay is long enough to guarantee that ProcessLifecycleOwner
+ * won't send any events if activities are destroyed and recreated due to a
+ * configuration change.
+ *
+ * It is useful for use cases where you would like to react on your app coming to the foreground or
+ * going to the background and you don't need a milliseconds accuracy in receiving lifecycle
+ * events.
+ */
+class ProcessLifecycleOwner private constructor() : LifecycleOwner {
+    // ground truth counters
+    private var startedCounter = 0
+    private var resumedCounter = 0
+    private var pauseSent = true
+    private var stopSent = true
+    private var handler: Handler? = null
+    private val registry = LifecycleRegistry(this)
+    private val delayedPauseRunnable = Runnable {
+        dispatchPauseIfNeeded()
+        dispatchStopIfNeeded()
+    }
+    private val initializationListener: ReportFragment.ActivityInitializationListener =
+        object : ReportFragment.ActivityInitializationListener {
+            override fun onCreate() {}
+
+            override fun onStart() {
+                activityStarted()
+            }
+
+            override fun onResume() {
+                activityResumed()
+            }
+        }
+
+    companion object {
+        @VisibleForTesting
+        internal const val TIMEOUT_MS: Long = 700 // mls
+        private val newInstance = ProcessLifecycleOwner()
+
+        /**
+         * The LifecycleOwner for the whole application process. Note that if your application
+         * has multiple processes, this provider does not know about other processes.
+         *
+         * @return [LifecycleOwner] for the whole application.
+         */
+        @JvmStatic
+        fun get(): LifecycleOwner {
+            return newInstance
+        }
+
+        @JvmStatic
+        internal fun init(context: Context) {
+            newInstance.attach(context)
+        }
+    }
+
+    internal fun activityStarted() {
+        startedCounter++
+        if (startedCounter == 1 && stopSent) {
+            registry.handleLifecycleEvent(Lifecycle.Event.ON_START)
+            stopSent = false
+        }
+    }
+
+    internal fun activityResumed() {
+        resumedCounter++
+        if (resumedCounter == 1) {
+            if (pauseSent) {
+                registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
+                pauseSent = false
+            } else {
+                handler!!.removeCallbacks(delayedPauseRunnable)
+            }
+        }
+    }
+
+    internal fun activityPaused() {
+        resumedCounter--
+        if (resumedCounter == 0) {
+            handler!!.postDelayed(delayedPauseRunnable, TIMEOUT_MS)
+        }
+    }
+
+    internal fun activityStopped() {
+        startedCounter--
+        dispatchStopIfNeeded()
+    }
+
+    internal fun dispatchPauseIfNeeded() {
+        if (resumedCounter == 0) {
+            pauseSent = true
+            registry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+        }
+    }
+
+    internal fun dispatchStopIfNeeded() {
+        if (startedCounter == 0 && pauseSent) {
+            registry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
+            stopSent = true
+        }
+    }
+
+    @Suppress("DEPRECATION")
+    internal fun attach(context: Context) {
+        handler = Handler()
+        registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
+        val app = context.applicationContext as Application
+        app.registerActivityLifecycleCallbacks(object : EmptyActivityLifecycleCallbacks() {
+            @RequiresApi(29)
+            override fun onActivityPreCreated(
+                activity: Activity,
+                savedInstanceState: Bundle?
+            ) {
+                // We need the ProcessLifecycleOwner to get ON_START and ON_RESUME precisely
+                // before the first activity gets its LifecycleOwner started/resumed.
+                // The activity's LifecycleOwner gets started/resumed via an activity registered
+                // callback added in onCreate(). By adding our own activity registered callback in
+                // onActivityPreCreated(), we get our callbacks first while still having the
+                // right relative order compared to the Activity's onStart()/onResume() callbacks.
+                Api29Impl.registerActivityLifecycleCallbacks(activity,
+                    object : EmptyActivityLifecycleCallbacks() {
+                        override fun onActivityPostStarted(activity: Activity) {
+                            activityStarted()
+                        }
+
+                        override fun onActivityPostResumed(activity: Activity) {
+                            activityResumed()
+                        }
+                    })
+            }
+
+            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
+                // Only use ReportFragment pre API 29 - after that, we can use the
+                // onActivityPostStarted and onActivityPostResumed callbacks registered in
+                // onActivityPreCreated()
+                if (Build.VERSION.SDK_INT < 29) {
+                    ReportFragment.get(activity).setProcessListener(initializationListener)
+                }
+            }
+
+            override fun onActivityPaused(activity: Activity) {
+                activityPaused()
+            }
+
+            override fun onActivityStopped(activity: Activity) {
+                activityStopped()
+            }
+        })
+    }
+
+    override fun getLifecycle(): Lifecycle {
+        return registry
+    }
+
+    @RequiresApi(29)
+    internal object Api29Impl {
+        @DoNotInline
+        @JvmStatic
+        fun registerActivityLifecycleCallbacks(
+            activity: Activity,
+            callback: Application.ActivityLifecycleCallbacks
+        ) {
+            activity.registerActivityLifecycleCallbacks(callback)
+        }
+    }
+}
\ No newline at end of file
diff --git a/lifecycle/lifecycle-service/api/current.txt b/lifecycle/lifecycle-service/api/current.txt
index 234d14a..a27283e 100644
--- a/lifecycle/lifecycle-service/api/current.txt
+++ b/lifecycle/lifecycle-service/api/current.txt
@@ -8,12 +8,13 @@
   }
 
   public class ServiceLifecycleDispatcher {
-    ctor public ServiceLifecycleDispatcher(androidx.lifecycle.LifecycleOwner);
+    ctor public ServiceLifecycleDispatcher(androidx.lifecycle.LifecycleOwner provider);
     method public androidx.lifecycle.Lifecycle getLifecycle();
     method public void onServicePreSuperOnBind();
     method public void onServicePreSuperOnCreate();
     method public void onServicePreSuperOnDestroy();
     method public void onServicePreSuperOnStart();
+    property public androidx.lifecycle.Lifecycle lifecycle;
   }
 
 }
diff --git a/lifecycle/lifecycle-service/api/public_plus_experimental_current.txt b/lifecycle/lifecycle-service/api/public_plus_experimental_current.txt
index 234d14a..a27283e 100644
--- a/lifecycle/lifecycle-service/api/public_plus_experimental_current.txt
+++ b/lifecycle/lifecycle-service/api/public_plus_experimental_current.txt
@@ -8,12 +8,13 @@
   }
 
   public class ServiceLifecycleDispatcher {
-    ctor public ServiceLifecycleDispatcher(androidx.lifecycle.LifecycleOwner);
+    ctor public ServiceLifecycleDispatcher(androidx.lifecycle.LifecycleOwner provider);
     method public androidx.lifecycle.Lifecycle getLifecycle();
     method public void onServicePreSuperOnBind();
     method public void onServicePreSuperOnCreate();
     method public void onServicePreSuperOnDestroy();
     method public void onServicePreSuperOnStart();
+    property public androidx.lifecycle.Lifecycle lifecycle;
   }
 
 }
diff --git a/lifecycle/lifecycle-service/api/restricted_current.txt b/lifecycle/lifecycle-service/api/restricted_current.txt
index 234d14a..a27283e 100644
--- a/lifecycle/lifecycle-service/api/restricted_current.txt
+++ b/lifecycle/lifecycle-service/api/restricted_current.txt
@@ -8,12 +8,13 @@
   }
 
   public class ServiceLifecycleDispatcher {
-    ctor public ServiceLifecycleDispatcher(androidx.lifecycle.LifecycleOwner);
+    ctor public ServiceLifecycleDispatcher(androidx.lifecycle.LifecycleOwner provider);
     method public androidx.lifecycle.Lifecycle getLifecycle();
     method public void onServicePreSuperOnBind();
     method public void onServicePreSuperOnCreate();
     method public void onServicePreSuperOnDestroy();
     method public void onServicePreSuperOnStart();
+    property public androidx.lifecycle.Lifecycle lifecycle;
   }
 
 }
diff --git a/lifecycle/lifecycle-service/src/main/java/androidx/lifecycle/ServiceLifecycleDispatcher.java b/lifecycle/lifecycle-service/src/main/java/androidx/lifecycle/ServiceLifecycleDispatcher.java
deleted file mode 100644
index 2a366ba..0000000
--- a/lifecycle/lifecycle-service/src/main/java/androidx/lifecycle/ServiceLifecycleDispatcher.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2017 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.lifecycle;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.Handler;
-
-import androidx.annotation.NonNull;
-
-/**
- * Helper class to dispatch lifecycle events for a service. Use it only if it is impossible
- * to use {@link LifecycleService}.
- */
-@SuppressWarnings("WeakerAccess")
-public class ServiceLifecycleDispatcher {
-    private final LifecycleRegistry mRegistry;
-    private final Handler mHandler;
-    private DispatchRunnable mLastDispatchRunnable;
-
-    /**
-     * @param provider {@link LifecycleOwner} for a service, usually it is a service itself
-     */
-    @SuppressWarnings("deprecation")
-    public ServiceLifecycleDispatcher(@NonNull LifecycleOwner provider) {
-        mRegistry = new LifecycleRegistry(provider);
-        mHandler = new Handler();
-    }
-
-    private void postDispatchRunnable(Lifecycle.Event event) {
-        if (mLastDispatchRunnable != null) {
-            mLastDispatchRunnable.run();
-        }
-        mLastDispatchRunnable = new DispatchRunnable(mRegistry, event);
-        mHandler.postAtFrontOfQueue(mLastDispatchRunnable);
-    }
-
-    /**
-     * Must be a first call in {@link Service#onCreate()} method, even before super.onCreate call.
-     */
-    public void onServicePreSuperOnCreate() {
-        postDispatchRunnable(Lifecycle.Event.ON_CREATE);
-    }
-
-    /**
-     * Must be a first call in {@link Service#onBind(Intent)} method, even before super.onBind
-     * call.
-     */
-    public void onServicePreSuperOnBind() {
-        postDispatchRunnable(Lifecycle.Event.ON_START);
-    }
-
-    /**
-     * Must be a first call in {@link Service#onStart(Intent, int)} or
-     * {@link Service#onStartCommand(Intent, int, int)} methods, even before
-     * a corresponding super call.
-     */
-    public void onServicePreSuperOnStart() {
-        postDispatchRunnable(Lifecycle.Event.ON_START);
-    }
-
-    /**
-     * Must be a first call in {@link Service#onDestroy()} method, even before super.OnDestroy
-     * call.
-     */
-    public void onServicePreSuperOnDestroy() {
-        postDispatchRunnable(Lifecycle.Event.ON_STOP);
-        postDispatchRunnable(Lifecycle.Event.ON_DESTROY);
-    }
-
-    /**
-     * @return {@link Lifecycle} for the given {@link LifecycleOwner}
-     */
-    @NonNull
-    public Lifecycle getLifecycle() {
-        return mRegistry;
-    }
-
-    static class DispatchRunnable implements Runnable {
-        private final LifecycleRegistry mRegistry;
-        final Lifecycle.Event mEvent;
-        private boolean mWasExecuted = false;
-
-        DispatchRunnable(@NonNull LifecycleRegistry registry, Lifecycle.Event event) {
-            mRegistry = registry;
-            mEvent = event;
-        }
-
-        @Override
-        public void run() {
-            if (!mWasExecuted) {
-                mRegistry.handleLifecycleEvent(mEvent);
-                mWasExecuted = true;
-            }
-        }
-    }
-}
diff --git a/lifecycle/lifecycle-service/src/main/java/androidx/lifecycle/ServiceLifecycleDispatcher.kt b/lifecycle/lifecycle-service/src/main/java/androidx/lifecycle/ServiceLifecycleDispatcher.kt
new file mode 100644
index 0000000..9d2d086
--- /dev/null
+++ b/lifecycle/lifecycle-service/src/main/java/androidx/lifecycle/ServiceLifecycleDispatcher.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 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.lifecycle
+
+import android.app.Service
+import android.os.Handler
+
+/**
+ * Helper class to dispatch lifecycle events for a Service. Use it only if it is impossible
+ * to use [LifecycleService].
+ *
+ * @param provider [LifecycleOwner] for a service, usually it is a service itself
+ */
+open class ServiceLifecycleDispatcher(provider: LifecycleOwner) {
+
+    private val registry: LifecycleRegistry
+    private val handler: Handler
+    private var lastDispatchRunnable: DispatchRunnable? = null
+
+    init {
+        registry = LifecycleRegistry(provider)
+        @Suppress("DEPRECATION")
+        handler = Handler()
+    }
+
+    private fun postDispatchRunnable(event: Lifecycle.Event) {
+        lastDispatchRunnable?.run()
+        lastDispatchRunnable = DispatchRunnable(registry, event)
+        handler.postAtFrontOfQueue(lastDispatchRunnable!!)
+    }
+
+    /**
+     * Must be a first call in [Service.onCreate] method, even before super.onCreate call.
+     */
+    open fun onServicePreSuperOnCreate() {
+        postDispatchRunnable(Lifecycle.Event.ON_CREATE)
+    }
+
+    /**
+     * Must be a first call in [Service.onBind] method, even before super.onBind
+     * call.
+     */
+    open fun onServicePreSuperOnBind() {
+        postDispatchRunnable(Lifecycle.Event.ON_START)
+    }
+
+    /**
+     * Must be a first call in [Service.onStart] or
+     * [Service.onStartCommand] methods, even before
+     * a corresponding super call.
+     */
+    open fun onServicePreSuperOnStart() {
+        postDispatchRunnable(Lifecycle.Event.ON_START)
+    }
+
+    /**
+     * Must be a first call in [Service.onDestroy] method, even before super.OnDestroy
+     * call.
+     */
+    open fun onServicePreSuperOnDestroy() {
+        postDispatchRunnable(Lifecycle.Event.ON_STOP)
+        postDispatchRunnable(Lifecycle.Event.ON_DESTROY)
+    }
+
+    /**
+     * [Lifecycle] for the given [LifecycleOwner]
+     */
+    open val lifecycle: Lifecycle
+        get() = registry
+
+    internal class DispatchRunnable(
+        private val registry: LifecycleRegistry,
+        val event: Lifecycle.Event
+    ) : Runnable {
+        private var wasExecuted = false
+
+        override fun run() {
+            if (!wasExecuted) {
+                registry.handleLifecycleEvent(event)
+                wasExecuted = true
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/lifecycle/lifecycle-viewmodel-compose/api/current.txt b/lifecycle/lifecycle-viewmodel-compose/api/current.txt
index 12fad78..05b6910 100644
--- a/lifecycle/lifecycle-viewmodel-compose/api/current.txt
+++ b/lifecycle/lifecycle-viewmodel-compose/api/current.txt
@@ -13,10 +13,10 @@
 
   public final class ViewModelKt {
     method @androidx.compose.runtime.Composable public static <VM extends androidx.lifecycle.ViewModel> VM viewModel(Class<VM> modelClass, optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key, optional androidx.lifecycle.ViewModelProvider.Factory? factory, optional androidx.lifecycle.viewmodel.CreationExtras extras);
-    method @Deprecated @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
-    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String key, optional androidx.lifecycle.ViewModelProvider.Factory? factory, optional androidx.lifecycle.viewmodel.CreationExtras extras);
-    method @Deprecated @androidx.compose.runtime.Composable public static <VM extends androidx.lifecycle.ViewModel> VM viewModel(Class<VM> modelClass, optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
-    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String key, kotlin.jvm.functions.Function1<? super androidx.lifecycle.viewmodel.CreationExtras,? extends VM> initializer);
+    method @Deprecated @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
+    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key, optional androidx.lifecycle.ViewModelProvider.Factory? factory, optional androidx.lifecycle.viewmodel.CreationExtras extras);
+    method @Deprecated @androidx.compose.runtime.Composable public static <VM extends androidx.lifecycle.ViewModel> VM viewModel(Class<VM> modelClass, optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
+    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key, kotlin.jvm.functions.Function1<? super androidx.lifecycle.viewmodel.CreationExtras,? extends VM> initializer);
   }
 
 }
diff --git a/lifecycle/lifecycle-viewmodel-compose/api/public_plus_experimental_current.txt b/lifecycle/lifecycle-viewmodel-compose/api/public_plus_experimental_current.txt
index fdd4c83..188b922 100644
--- a/lifecycle/lifecycle-viewmodel-compose/api/public_plus_experimental_current.txt
+++ b/lifecycle/lifecycle-viewmodel-compose/api/public_plus_experimental_current.txt
@@ -20,10 +20,10 @@
 
   public final class ViewModelKt {
     method @androidx.compose.runtime.Composable public static <VM extends androidx.lifecycle.ViewModel> VM viewModel(Class<VM> modelClass, optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key, optional androidx.lifecycle.ViewModelProvider.Factory? factory, optional androidx.lifecycle.viewmodel.CreationExtras extras);
-    method @Deprecated @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
-    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String key, optional androidx.lifecycle.ViewModelProvider.Factory? factory, optional androidx.lifecycle.viewmodel.CreationExtras extras);
-    method @Deprecated @androidx.compose.runtime.Composable public static <VM extends androidx.lifecycle.ViewModel> VM viewModel(Class<VM> modelClass, optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
-    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String key, kotlin.jvm.functions.Function1<? super androidx.lifecycle.viewmodel.CreationExtras,? extends VM> initializer);
+    method @Deprecated @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
+    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key, optional androidx.lifecycle.ViewModelProvider.Factory? factory, optional androidx.lifecycle.viewmodel.CreationExtras extras);
+    method @Deprecated @androidx.compose.runtime.Composable public static <VM extends androidx.lifecycle.ViewModel> VM viewModel(Class<VM> modelClass, optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
+    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key, kotlin.jvm.functions.Function1<? super androidx.lifecycle.viewmodel.CreationExtras,? extends VM> initializer);
   }
 
 }
diff --git a/lifecycle/lifecycle-viewmodel-compose/api/restricted_current.txt b/lifecycle/lifecycle-viewmodel-compose/api/restricted_current.txt
index 12fad78..05b6910 100644
--- a/lifecycle/lifecycle-viewmodel-compose/api/restricted_current.txt
+++ b/lifecycle/lifecycle-viewmodel-compose/api/restricted_current.txt
@@ -13,10 +13,10 @@
 
   public final class ViewModelKt {
     method @androidx.compose.runtime.Composable public static <VM extends androidx.lifecycle.ViewModel> VM viewModel(Class<VM> modelClass, optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key, optional androidx.lifecycle.ViewModelProvider.Factory? factory, optional androidx.lifecycle.viewmodel.CreationExtras extras);
-    method @Deprecated @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
-    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String key, optional androidx.lifecycle.ViewModelProvider.Factory? factory, optional androidx.lifecycle.viewmodel.CreationExtras extras);
-    method @Deprecated @androidx.compose.runtime.Composable public static <VM extends androidx.lifecycle.ViewModel> VM viewModel(Class<VM> modelClass, optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
-    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String key, kotlin.jvm.functions.Function1<? super androidx.lifecycle.viewmodel.CreationExtras,? extends VM> initializer);
+    method @Deprecated @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
+    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key, optional androidx.lifecycle.ViewModelProvider.Factory? factory, optional androidx.lifecycle.viewmodel.CreationExtras extras);
+    method @Deprecated @androidx.compose.runtime.Composable public static <VM extends androidx.lifecycle.ViewModel> VM viewModel(Class<VM> modelClass, optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key, optional androidx.lifecycle.ViewModelProvider.Factory? factory);
+    method @androidx.compose.runtime.Composable public static inline <reified VM extends androidx.lifecycle.ViewModel> VM viewModel(optional androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, optional String? key, kotlin.jvm.functions.Function1<? super androidx.lifecycle.viewmodel.CreationExtras,? extends VM> initializer);
   }
 
 }
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/api/current.ignore b/lifecycle/lifecycle-viewmodel-savedstate/api/current.ignore
new file mode 100644
index 0000000..3bd1b51
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-savedstate/api/current.ignore
@@ -0,0 +1,5 @@
+// Baseline format: 1.0
+RemovedMethod: androidx.lifecycle.AbstractSavedStateViewModelFactory#create(Class<T>):
+    Removed method androidx.lifecycle.AbstractSavedStateViewModelFactory.create(Class<T>)
+RemovedMethod: androidx.lifecycle.AbstractSavedStateViewModelFactory#create(Class<T>, androidx.lifecycle.viewmodel.CreationExtras):
+    Removed method androidx.lifecycle.AbstractSavedStateViewModelFactory.create(Class<T>,androidx.lifecycle.viewmodel.CreationExtras)
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/api/current.txt b/lifecycle/lifecycle-viewmodel-savedstate/api/current.txt
index 35fdaaa..c030c8a 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/api/current.txt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/api/current.txt
@@ -3,10 +3,8 @@
 
   public abstract class AbstractSavedStateViewModelFactory implements androidx.lifecycle.ViewModelProvider.Factory {
     ctor public AbstractSavedStateViewModelFactory();
-    ctor public AbstractSavedStateViewModelFactory(androidx.savedstate.SavedStateRegistryOwner, android.os.Bundle?);
-    method public final <T extends androidx.lifecycle.ViewModel> T create(Class<T!>, androidx.lifecycle.viewmodel.CreationExtras);
-    method public final <T extends androidx.lifecycle.ViewModel> T create(Class<T!>);
-    method protected abstract <T extends androidx.lifecycle.ViewModel> T create(String, Class<T!>, androidx.lifecycle.SavedStateHandle);
+    ctor public AbstractSavedStateViewModelFactory(androidx.savedstate.SavedStateRegistryOwner owner, android.os.Bundle? defaultArgs);
+    method protected abstract <T extends androidx.lifecycle.ViewModel> T create(String key, Class<T> modelClass, androidx.lifecycle.SavedStateHandle handle);
   }
 
   public final class SavedStateHandle {
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/api/public_plus_experimental_current.txt b/lifecycle/lifecycle-viewmodel-savedstate/api/public_plus_experimental_current.txt
index 35fdaaa..c030c8a 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/api/public_plus_experimental_current.txt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/api/public_plus_experimental_current.txt
@@ -3,10 +3,8 @@
 
   public abstract class AbstractSavedStateViewModelFactory implements androidx.lifecycle.ViewModelProvider.Factory {
     ctor public AbstractSavedStateViewModelFactory();
-    ctor public AbstractSavedStateViewModelFactory(androidx.savedstate.SavedStateRegistryOwner, android.os.Bundle?);
-    method public final <T extends androidx.lifecycle.ViewModel> T create(Class<T!>, androidx.lifecycle.viewmodel.CreationExtras);
-    method public final <T extends androidx.lifecycle.ViewModel> T create(Class<T!>);
-    method protected abstract <T extends androidx.lifecycle.ViewModel> T create(String, Class<T!>, androidx.lifecycle.SavedStateHandle);
+    ctor public AbstractSavedStateViewModelFactory(androidx.savedstate.SavedStateRegistryOwner owner, android.os.Bundle? defaultArgs);
+    method protected abstract <T extends androidx.lifecycle.ViewModel> T create(String key, Class<T> modelClass, androidx.lifecycle.SavedStateHandle handle);
   }
 
   public final class SavedStateHandle {
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.ignore b/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.ignore
new file mode 100644
index 0000000..3bd1b51
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.ignore
@@ -0,0 +1,5 @@
+// Baseline format: 1.0
+RemovedMethod: androidx.lifecycle.AbstractSavedStateViewModelFactory#create(Class<T>):
+    Removed method androidx.lifecycle.AbstractSavedStateViewModelFactory.create(Class<T>)
+RemovedMethod: androidx.lifecycle.AbstractSavedStateViewModelFactory#create(Class<T>, androidx.lifecycle.viewmodel.CreationExtras):
+    Removed method androidx.lifecycle.AbstractSavedStateViewModelFactory.create(Class<T>,androidx.lifecycle.viewmodel.CreationExtras)
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.txt b/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.txt
index 35fdaaa..c030c8a 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.txt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.txt
@@ -3,10 +3,8 @@
 
   public abstract class AbstractSavedStateViewModelFactory implements androidx.lifecycle.ViewModelProvider.Factory {
     ctor public AbstractSavedStateViewModelFactory();
-    ctor public AbstractSavedStateViewModelFactory(androidx.savedstate.SavedStateRegistryOwner, android.os.Bundle?);
-    method public final <T extends androidx.lifecycle.ViewModel> T create(Class<T!>, androidx.lifecycle.viewmodel.CreationExtras);
-    method public final <T extends androidx.lifecycle.ViewModel> T create(Class<T!>);
-    method protected abstract <T extends androidx.lifecycle.ViewModel> T create(String, Class<T!>, androidx.lifecycle.SavedStateHandle);
+    ctor public AbstractSavedStateViewModelFactory(androidx.savedstate.SavedStateRegistryOwner owner, android.os.Bundle? defaultArgs);
+    method protected abstract <T extends androidx.lifecycle.ViewModel> T create(String key, Class<T> modelClass, androidx.lifecycle.SavedStateHandle handle);
   }
 
   public final class SavedStateHandle {
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/AbstractSavedStateViewModelFactory.java b/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/AbstractSavedStateViewModelFactory.java
deleted file mode 100644
index dad9f6f..0000000
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/AbstractSavedStateViewModelFactory.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright 2018 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.lifecycle;
-
-import static androidx.lifecycle.LegacySavedStateHandleController.attachHandleIfNeeded;
-import static androidx.lifecycle.ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY;
-
-import android.annotation.SuppressLint;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.lifecycle.viewmodel.CreationExtras;
-import androidx.savedstate.SavedStateRegistry;
-import androidx.savedstate.SavedStateRegistryOwner;
-
-/**
- * Skeleton of androidx.lifecycle.ViewModelProvider.KeyedFactory
- * that creates {@link SavedStateHandle} for every requested {@link androidx.lifecycle.ViewModel}.
- * The subclasses implement {@link #create(String, Class, SavedStateHandle)} to actually instantiate
- * {@code androidx.lifecycle.ViewModel}s.
- */
-public abstract class AbstractSavedStateViewModelFactory extends ViewModelProvider.OnRequeryFactory
-        implements ViewModelProvider.Factory {
-    static final String TAG_SAVED_STATE_HANDLE_CONTROLLER = "androidx.lifecycle.savedstate.vm.tag";
-
-    private SavedStateRegistry mSavedStateRegistry;
-    private Lifecycle mLifecycle;
-    private Bundle mDefaultArgs;
-
-    /**
-     * Constructs this factory.
-     * <p>
-     * When a factory is constructed this way, a component for which {@link SavedStateHandle} is
-     * scoped must have called
-     * {@link SavedStateHandleSupport#enableSavedStateHandles(SavedStateRegistryOwner)}.
-     * See {@link SavedStateHandleSupport#createSavedStateHandle(CreationExtras)} docs for more
-     * details.
-     */
-    public AbstractSavedStateViewModelFactory() {
-    }
-
-    /**
-     * Constructs this factory.
-     *
-     * @param owner {@link SavedStateRegistryOwner} that will provide restored state for created
-     * {@link androidx.lifecycle.ViewModel ViewModels}
-     * @param defaultArgs values from this {@code Bundle} will be used as defaults by
-     *                    {@link SavedStateHandle} passed in {@link ViewModel ViewModels}
-     *                    if there is no previously saved state
-     *                    or previously saved state misses a value by such key
-     */
-    @SuppressLint("LambdaLast")
-    public AbstractSavedStateViewModelFactory(@NonNull SavedStateRegistryOwner owner,
-            @Nullable Bundle defaultArgs) {
-        mSavedStateRegistry = owner.getSavedStateRegistry();
-        mLifecycle = owner.getLifecycle();
-        mDefaultArgs = defaultArgs;
-    }
-
-    @NonNull
-    @Override
-    public final <T extends ViewModel> T create(@NonNull Class<T> modelClass,
-            @NonNull CreationExtras extras) {
-        String key = extras.get(VIEW_MODEL_KEY);
-        if (key == null) {
-            throw new IllegalStateException(
-                    "VIEW_MODEL_KEY must always be provided by ViewModelProvider");
-        }
-        // if a factory constructed in the old way use the old infra to create SavedStateHandle
-        if (mSavedStateRegistry != null) {
-            return create(key, modelClass);
-        } else {
-            return create(key, modelClass, SavedStateHandleSupport.createSavedStateHandle(extras));
-        }
-    }
-
-    @NonNull
-    private <T extends ViewModel> T create(@NonNull String key, @NonNull Class<T> modelClass) {
-        SavedStateHandleController controller = LegacySavedStateHandleController
-                .create(mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
-        T viewmodel = create(key, modelClass, controller.getHandle());
-        viewmodel.setTagIfAbsent(TAG_SAVED_STATE_HANDLE_CONTROLLER, controller);
-        return viewmodel;
-    }
-
-    @NonNull
-    @Override
-    public final <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
-        // ViewModelProvider calls correct create that support same modelClass with different keys
-        // If a developer manually calls this method, there is no "key" in picture, so factory
-        // simply uses classname internally as as key.
-        String canonicalName = modelClass.getCanonicalName();
-        if (canonicalName == null) {
-            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
-        }
-        if (mLifecycle == null) {
-            throw new UnsupportedOperationException(
-                    "AbstractSavedStateViewModelFactory constructed "
-                            + "with empty constructor supports only calls to "
-                            +   "create(modelClass: Class<T>, extras: CreationExtras)."
-            );
-        }
-        return create(canonicalName, modelClass);
-    }
-
-    /**
-     * Creates a new instance of the given {@code Class}.
-     * <p>
-     *
-     * @param key a key associated with the requested ViewModel
-     * @param modelClass a {@code Class} whose instance is requested
-     * @param handle a handle to saved state associated with the requested ViewModel
-     * @param <T> The type parameter for the ViewModel.
-     * @return a newly created ViewModels
-     */
-    @NonNull
-    protected abstract <T extends ViewModel> T create(@NonNull String key,
-            @NonNull Class<T> modelClass, @NonNull SavedStateHandle handle);
-
-    /**
-     * @hide
-     */
-    @Override
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public void onRequery(@NonNull ViewModel viewModel) {
-        // is need only for legacy path
-        if (mSavedStateRegistry != null) {
-            attachHandleIfNeeded(viewModel, mSavedStateRegistry, mLifecycle);
-        }
-    }
-}
-
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/AbstractSavedStateViewModelFactory.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/AbstractSavedStateViewModelFactory.kt
new file mode 100644
index 0000000..6561594
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/AbstractSavedStateViewModelFactory.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2018 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.lifecycle
+
+import android.os.Bundle
+import androidx.annotation.RestrictTo
+import androidx.lifecycle.LegacySavedStateHandleController.attachHandleIfNeeded
+import androidx.lifecycle.viewmodel.CreationExtras
+import androidx.savedstate.SavedStateRegistry
+import androidx.savedstate.SavedStateRegistryOwner
+
+/**
+ * Skeleton of androidx.lifecycle.ViewModelProvider.KeyedFactory
+ * that creates [SavedStateHandle] for every requested [ViewModel].
+ * The subclasses implement [create] to actually instantiate
+ * `androidx.lifecycle.ViewModel`s.
+ */
+public abstract class AbstractSavedStateViewModelFactory :
+    ViewModelProvider.OnRequeryFactory,
+    ViewModelProvider.Factory {
+
+    private var savedStateRegistry: SavedStateRegistry? = null
+    private var lifecycle: Lifecycle? = null
+    private var defaultArgs: Bundle? = null
+
+    /**
+     * Constructs this factory.
+     *
+     * When a factory is constructed this way, a component for which [SavedStateHandle] is
+     * scoped must have called
+     * [SavedStateHandleSupport.enableSavedStateHandles].
+     * See [CreationExtras.createSavedStateHandle] docs for more
+     * details.
+     */
+    constructor() {}
+
+    /**
+     * Constructs this factory.
+     *
+     * @param owner [SavedStateRegistryOwner] that will provide restored state for created
+     * [ViewModels][ViewModel]
+     * @param defaultArgs values from this `Bundle` will be used as defaults by
+     * [SavedStateHandle] passed in [ViewModels][ViewModel] if there is no
+     * previously saved state or previously saved state misses a value by such key
+     */
+    constructor(
+        owner: SavedStateRegistryOwner,
+        defaultArgs: Bundle?
+    ) {
+        savedStateRegistry = owner.savedStateRegistry
+        lifecycle = owner.lifecycle
+        this.defaultArgs = defaultArgs
+    }
+
+    /**
+     * Creates a new instance of the given `Class`.
+     *
+     * @param modelClass a `Class` whose instance is requested
+     * @param extras an additional information for this creation request
+     *
+     * @return a newly created ViewModel
+     *
+     * @throws IllegalStateException if no VIEW_MODEL_KEY provided by ViewModelProvider
+     */
+    public override fun <T : ViewModel> create(
+        modelClass: Class<T>,
+        extras: CreationExtras
+    ): T {
+        val key = extras[ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY]
+            ?: throw IllegalStateException(
+                "VIEW_MODEL_KEY must always be provided by ViewModelProvider"
+            )
+        // if a factory constructed in the old way use the old infra to create SavedStateHandle
+        return if (savedStateRegistry != null) {
+            create(key, modelClass)
+        } else {
+            create(key, modelClass, extras.createSavedStateHandle())
+        }
+    }
+
+    private fun <T : ViewModel> create(key: String, modelClass: Class<T>): T {
+        val controller = LegacySavedStateHandleController
+            .create(savedStateRegistry, lifecycle, key, defaultArgs)
+        val viewModel = create(key, modelClass, controller.handle)
+        viewModel.setTagIfAbsent(TAG_SAVED_STATE_HANDLE_CONTROLLER, controller)
+        return viewModel
+    }
+
+    /**
+     * Creates a new instance of the given `Class`.
+     *
+     * @param modelClass a `Class` whose instance is requested
+     *
+     * @return a newly created ViewModel
+     *
+     * @throws IllegalArgumentException if the given [modelClass] is local or anonymous class.
+     * @throws UnsupportedOperationException if AbstractSavedStateViewModelFactory constructed
+     * with empty constructor, therefore no [SavedStateRegistryOwner] available for lifecycle
+     */
+    public override fun <T : ViewModel> create(modelClass: Class<T>): T {
+        // ViewModelProvider calls correct create that support same modelClass with different keys
+        // If a developer manually calls this method, there is no "key" in picture, so factory
+        // simply uses classname internally as as key.
+        val canonicalName = modelClass.canonicalName
+            ?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
+        if (lifecycle == null) {
+            throw UnsupportedOperationException(
+                "AbstractSavedStateViewModelFactory constructed " +
+                    "with empty constructor supports only calls to " +
+                    "create(modelClass: Class<T>, extras: CreationExtras)."
+            )
+        }
+        return create(canonicalName, modelClass)
+    }
+
+    /**
+     * Creates a new instance of the given `Class`.
+     *
+     * @param key a key associated with the requested ViewModel
+     * @param modelClass a `Class` whose instance is requested
+     * @param handle a handle to saved state associated with the requested ViewModel
+     *
+     * @return the newly created ViewModel
+    </T> */
+    protected abstract fun <T : ViewModel> create(
+        key: String,
+        modelClass: Class<T>,
+        handle: SavedStateHandle
+    ): T
+
+    /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    override fun onRequery(viewModel: ViewModel) {
+        // is need only for legacy path
+        if (savedStateRegistry != null) {
+            attachHandleIfNeeded(viewModel, savedStateRegistry, lifecycle)
+        }
+    }
+
+    internal companion object {
+        internal const val TAG_SAVED_STATE_HANDLE_CONTROLLER =
+            "androidx.lifecycle.savedstate.vm.tag"
+    }
+}
\ No newline at end of file
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandleController.java b/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandleController.java
deleted file mode 100644
index 33797c7..0000000
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandleController.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.lifecycle;
-
-import androidx.annotation.NonNull;
-import androidx.savedstate.SavedStateRegistry;
-
-final class SavedStateHandleController implements LifecycleEventObserver {
-    private final String mKey;
-    private boolean mIsAttached = false;
-    private final SavedStateHandle mHandle;
-
-    SavedStateHandleController(String key, SavedStateHandle handle) {
-        mKey = key;
-        mHandle = handle;
-    }
-
-    boolean isAttached() {
-        return mIsAttached;
-    }
-
-    void attachToLifecycle(SavedStateRegistry registry, Lifecycle lifecycle) {
-        if (mIsAttached) {
-            throw new IllegalStateException("Already attached to lifecycleOwner");
-        }
-        mIsAttached = true;
-        lifecycle.addObserver(this);
-        registry.registerSavedStateProvider(mKey, mHandle.savedStateProvider());
-    }
-
-    @Override
-    public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
-        if (event == Lifecycle.Event.ON_DESTROY) {
-            mIsAttached = false;
-            source.getLifecycle().removeObserver(this);
-        }
-    }
-
-    SavedStateHandle getHandle() {
-        return mHandle;
-    }
-}
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandleController.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandleController.kt
new file mode 100644
index 0000000..d50f2c7
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/main/java/androidx/lifecycle/SavedStateHandleController.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.lifecycle
+
+import androidx.savedstate.SavedStateRegistry
+
+internal class SavedStateHandleController(
+    private val key: String,
+    val handle: SavedStateHandle
+) : LifecycleEventObserver {
+
+    var isAttached = false
+        private set
+
+    fun attachToLifecycle(registry: SavedStateRegistry, lifecycle: Lifecycle) {
+        check(!isAttached) { "Already attached to lifecycleOwner" }
+        isAttached = true
+        lifecycle.addObserver(this)
+        registry.registerSavedStateProvider(key, handle.savedStateProvider())
+    }
+
+    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+        if (event === Lifecycle.Event.ON_DESTROY) {
+            isAttached = false
+            source.lifecycle.removeObserver(this)
+        }
+    }
+}
\ No newline at end of file
diff --git a/lifecycle/lifecycle-viewmodel/api/current.txt b/lifecycle/lifecycle-viewmodel/api/current.txt
index 22bb4a9..577a5be 100644
--- a/lifecycle/lifecycle-viewmodel/api/current.txt
+++ b/lifecycle/lifecycle-viewmodel/api/current.txt
@@ -2,7 +2,7 @@
 package androidx.lifecycle {
 
   public class AndroidViewModel extends androidx.lifecycle.ViewModel {
-    ctor public AndroidViewModel(android.app.Application);
+    ctor public AndroidViewModel(android.app.Application application);
     method public <T extends android.app.Application> T getApplication();
   }
 
diff --git a/lifecycle/lifecycle-viewmodel/api/public_plus_experimental_current.txt b/lifecycle/lifecycle-viewmodel/api/public_plus_experimental_current.txt
index 22bb4a9..577a5be 100644
--- a/lifecycle/lifecycle-viewmodel/api/public_plus_experimental_current.txt
+++ b/lifecycle/lifecycle-viewmodel/api/public_plus_experimental_current.txt
@@ -2,7 +2,7 @@
 package androidx.lifecycle {
 
   public class AndroidViewModel extends androidx.lifecycle.ViewModel {
-    ctor public AndroidViewModel(android.app.Application);
+    ctor public AndroidViewModel(android.app.Application application);
     method public <T extends android.app.Application> T getApplication();
   }
 
diff --git a/lifecycle/lifecycle-viewmodel/api/restricted_current.txt b/lifecycle/lifecycle-viewmodel/api/restricted_current.txt
index 22bb4a9..577a5be 100644
--- a/lifecycle/lifecycle-viewmodel/api/restricted_current.txt
+++ b/lifecycle/lifecycle-viewmodel/api/restricted_current.txt
@@ -2,7 +2,7 @@
 package androidx.lifecycle {
 
   public class AndroidViewModel extends androidx.lifecycle.ViewModel {
-    ctor public AndroidViewModel(android.app.Application);
+    ctor public AndroidViewModel(android.app.Application application);
     method public <T extends android.app.Application> T getApplication();
   }
 
diff --git a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/AndroidViewModel.java b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/AndroidViewModel.java
deleted file mode 100644
index f3fc032..0000000
--- a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/AndroidViewModel.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2017 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.lifecycle;
-
-import android.annotation.SuppressLint;
-import android.app.Application;
-
-import androidx.annotation.NonNull;
-
-/**
- * Application context aware {@link ViewModel}.
- * <p>
- * Subclasses must have a constructor which accepts {@link Application} as the only parameter.
- * <p>
- */
-public class AndroidViewModel extends ViewModel {
-    @SuppressLint("StaticFieldLeak")
-    private Application mApplication;
-
-    public AndroidViewModel(@NonNull Application application) {
-        mApplication = application;
-    }
-
-    /**
-     * Return the application.
-     */
-    @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
-    @NonNull
-    public <T extends Application> T getApplication() {
-        return (T) mApplication;
-    }
-}
diff --git a/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/AndroidViewModel.kt b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/AndroidViewModel.kt
new file mode 100644
index 0000000..12f0b18
--- /dev/null
+++ b/lifecycle/lifecycle-viewmodel/src/main/java/androidx/lifecycle/AndroidViewModel.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 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.lifecycle
+
+import android.app.Application
+
+/**
+ * Application context aware [ViewModel].
+ *
+ * Subclasses must have a constructor which accepts [Application] as the only parameter.
+ */
+open class AndroidViewModel(private val application: Application) : ViewModel() {
+
+    /**
+     * Return the application.
+     */
+    @Suppress("UNCHECKED_CAST")
+    open fun <T : Application> getApplication(): T {
+        return application as T
+    }
+}
\ No newline at end of file
diff --git a/lint-checks/integration-tests/src/main/java/sample/annotation/provider/ExperimentalSampleAnnotationJava.java b/lint-checks/integration-tests/src/main/java/sample/annotation/provider/ExperimentalSampleAnnotationJava.java
index d04506a..5cf74d9 100644
--- a/lint-checks/integration-tests/src/main/java/sample/annotation/provider/ExperimentalSampleAnnotationJava.java
+++ b/lint-checks/integration-tests/src/main/java/sample/annotation/provider/ExperimentalSampleAnnotationJava.java
@@ -22,8 +22,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
-@SuppressWarnings("deprecation")
-@kotlin.Experimental
+@kotlin.RequiresOptIn
 @Retention(CLASS)
 @Target({ElementType.TYPE, ElementType.METHOD})
 public @interface ExperimentalSampleAnnotationJava {}
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java
index 4f186c0..c12fa6e 100644
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java
+++ b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayer2Test.java
@@ -1271,6 +1271,7 @@
         return cp;
     }
 
+    @FlakyTest(bugId = 264905014)
     @Test
     @LargeTest
     public void getTimestamp() throws Exception {
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProvider.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProvider.java
index ed033e1..34f191f 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProvider.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProvider.java
@@ -602,6 +602,10 @@
          *                   the route controller.
          * @param dynamicRoutes The dynamic route descriptors for published routes.
          *                      At least a selected or selecting route should be included.
+         *
+         * @throws IllegalArgumentException Thrown when no dynamic route descriptors are {@link
+         * DynamicRouteDescriptor#SELECTED SELECTED} or {@link DynamicRouteDescriptor#SELECTING
+         * SELECTING}.
          */
         public final void notifyDynamicRoutesChanged(
                 @NonNull MediaRouteDescriptor groupRoute,
@@ -612,6 +616,23 @@
             if (dynamicRoutes == null) {
                 throw new NullPointerException("dynamicRoutes must not be null");
             }
+
+            boolean hasSelectedRoute = false;
+            for (DynamicRouteDescriptor route: dynamicRoutes) {
+                int state = route.getSelectionState();
+                if (state == DynamicRouteDescriptor.SELECTED
+                        || state == DynamicRouteDescriptor.SELECTING) {
+                    hasSelectedRoute = true;
+                    break;
+                }
+            }
+
+            if (!hasSelectedRoute) {
+                throw new IllegalArgumentException("dynamicRoutes must have at least one selected"
+                        + " or selecting route.");
+
+            }
+
             synchronized (mLock) {
                 if (mExecutor != null) {
                     final OnDynamicRoutesChangedListener listener = mListener;
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
index fd67cc1..d16d702 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavDestination.kt
@@ -26,6 +26,7 @@
 import androidx.collection.SparseArrayCompat
 import androidx.collection.valueIterator
 import androidx.core.content.res.use
+import androidx.core.net.toUri
 import androidx.navigation.common.R
 import java.util.regex.Pattern
 import kotlin.reflect.KClass
@@ -410,6 +411,54 @@
     }
 
     /**
+     * Returns true if the [NavBackStackEntry.destination] contains the route.
+     *
+     * The route may be either:
+     * 1. an exact route without arguments
+     * 2. a route containing arguments where no arguments are filled in
+     * 3. a route containing arguments where some or all arguments are filled in
+     *
+     * In the case of 3., it will only match if the entry arguments
+     * match exactly with the arguments that were filled in inside the route.
+     *
+     * @param [route] The route to match with the route of this destination
+     *
+     * @param [arguments] The [NavBackStackEntry.arguments] of the entry for this destination.
+     *
+     * @param [graph] The [NavGraph] of the backStack containing the entry for this destination.
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public fun hasRoute(route: String, arguments: Bundle?, graph: NavGraph): Boolean {
+        // this matches based on routePattern
+        if (this.route == route) return true
+
+        // if no match based on routePattern, this means route contains filled in args.
+        val request = NavDeepLinkRequest.Builder.fromUri(createRoute(route).toUri()).build()
+        val matchingDeepLink = graph.matchDeepLink(request)
+
+        // If matchingDeepLink is null or it has no matching args, the route does not contain
+        // filled in args. Since it didn't match with routePattern earlier, we just return false.
+        val matchingArgs = matchingDeepLink?.matchingArgs
+        if (matchingArgs == null || matchingArgs.isEmpty) return false
+
+        // any args (partially or completely filled in) must exactly match between
+        // the route and entry's route
+        matchingArgs.keySet().forEach { key ->
+            if (this != matchingDeepLink.destination || arguments == null ||
+                !arguments.containsKey(key)
+            ) {
+                return false
+            }
+            val type = matchingDeepLink.destination.arguments[key]?.type
+            val routeArgValue = type?.get(matchingArgs, key)
+            val entryArgValue = type?.get(arguments, key)
+            if (routeArgValue == null || entryArgValue == null || routeArgValue != entryArgValue)
+                return false
+        }
+        return true
+    }
+
+    /**
      * @return Whether this NavDestination supports outgoing actions
      * @see NavDestination.putAction
      * @suppress
diff --git a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavControllerWithFragmentTest.kt b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavControllerWithFragmentTest.kt
index 8103e71..66de182 100644
--- a/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavControllerWithFragmentTest.kt
+++ b/navigation/navigation-fragment/src/androidTest/java/androidx/navigation/fragment/NavControllerWithFragmentTest.kt
@@ -16,6 +16,9 @@
 
 package androidx.navigation.fragment
 
+import android.app.Dialog
+import androidx.fragment.app.DialogFragment
+import android.os.Bundle
 import androidx.navigation.NavOptions
 import androidx.navigation.fragment.test.NavigationActivity
 import androidx.test.core.app.ActivityScenario
@@ -25,6 +28,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import androidx.navigation.fragment.test.R
+import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 
 @LargeTest
@@ -32,30 +36,203 @@
 class NavControllerWithFragmentTest {
 
     @Test
-    fun navigateWithSingleTop() {
+    fun fragmentNavigateWithSingleTop() = withNavigationActivity {
+        navController.navigate(R.id.empty_fragment)
+
+        val fm =
+            supportFragmentManager.findFragmentById(R.id.nav_host)?.childFragmentManager
+        fm?.executePendingTransactions()
+        val fragment = fm?.findFragmentById(R.id.nav_host)
+
+        navController.navigate(
+            R.id.empty_fragment,
+            null,
+            NavOptions.Builder().setLaunchSingleTop(true).build()
+        )
+
+        fm?.executePendingTransactions()
+
+        val replacementFragment = fm?.findFragmentById(R.id.nav_host)
+
+        assertWithMessage("Replacement should be a new instance")
+            .that(replacementFragment)
+            .isNotSameInstanceAs(fragment)
+    }
+
+    @Test
+    fun dialogFragmentNavigate_singleTop() = withNavigationActivity {
+        val navigator =
+            navController.navigatorProvider.getNavigator(DialogFragmentNavigator::class.java)
+        navController.navigate(R.id.testDialog_fragment)
+
+        val fm = supportFragmentManager.findFragmentById(R.id.nav_host)?.childFragmentManager
+        fm?.executePendingTransactions()
+
+        assertThat(navController.currentBackStackEntry?.destination?.id)
+            .isEqualTo(R.id.testDialog_fragment)
+        val originalFragment = fm?.findFragmentByTag(navController.currentBackStackEntry?.id)
+            as? TestDialogFragment
+        assertThat(originalFragment!!.dialogs.first().isShowing).isTrue()
+
+        // backStacks should be in sync
+        assertThat(navigator.backStack.value.size).isEqualTo(1)
+        assertThat(fm.fragments.size).isEqualTo(2) // start + dialog fragment
+
+        // singleTop navigation
+        navController.navigate(
+            R.id.testDialog_fragment,
+            null,
+            NavOptions.Builder().setLaunchSingleTop(true).build()
+        )
+        assertThat(navController.currentBackStackEntry?.destination?.id)
+            .isEqualTo(R.id.testDialog_fragment)
+        fm.executePendingTransactions()
+
+        assertThat(navController.currentBackStackEntry?.destination?.id)
+            .isEqualTo(R.id.testDialog_fragment)
+        val replacementFragment = fm.findFragmentByTag(navController.currentBackStackEntry?.id)
+            as? TestDialogFragment
+        // the first dialog should be dismissed
+        assertThat(originalFragment.dialogs.first().isShowing).isFalse()
+        assertThat(replacementFragment!!.dialogs.first().isShowing).isTrue()
+
+        assertWithMessage("Replacement should be a new instance")
+            .that(originalFragment)
+            .isNotSameInstanceAs(replacementFragment)
+
+        // backStacks should be in sync
+        assertThat(navigator.backStack.value.size).isEqualTo(1)
+        assertThat(fm.fragments.size).isEqualTo(2) // start + dialog fragment
+    }
+
+    @Test
+    fun dialogFragmentNavigate_immediateSingleTop() = withNavigationActivity {
+        val navigator =
+            navController.navigatorProvider.getNavigator(DialogFragmentNavigator::class.java)
+
+        // first navigation
+        navController.navigate(R.id.testDialog_fragment)
+
+        // immediate second navigation with singleTop without executing first transaction
+        navController.navigate(
+            R.id.testDialog_fragment,
+            null,
+            NavOptions.Builder().setLaunchSingleTop(true).build()
+        )
+        assertThat(navController.currentBackStackEntry?.destination?.id)
+            .isEqualTo(R.id.testDialog_fragment)
+
+        val fm = supportFragmentManager.findFragmentById(R.id.nav_host)?.childFragmentManager
+        fm?.executePendingTransactions()
+
+        assertThat(navController.currentBackStackEntry?.destination?.id)
+            .isEqualTo(R.id.testDialog_fragment)
+        val replacementFragment = fm?.findFragmentByTag(navController.currentBackStackEntry?.id)
+            as? TestDialogFragment
+
+        assertThat(replacementFragment!!.dialogs.first().isShowing).isTrue()
+
+        // ensure original Fragment is dismissed and backStacks are in sync
+        assertThat(navigator.backStack.value.size).isEqualTo(1)
+        assertThat(fm.fragments.size).isEqualTo(2) // start + dialog fragment
+    }
+
+    @Test
+    fun dialogFragmentNavigate_multiImmediateSingleTop() = withNavigationActivity {
+        val navigator =
+            navController.navigatorProvider.getNavigator(DialogFragmentNavigator::class.java)
+
+        // first navigation
+        navController.navigate(R.id.testDialog_fragment)
+
+        // immediate second navigation with singleTop without executing first transaction
+        navController.navigate(
+            R.id.testDialog_fragment,
+            null,
+            NavOptions.Builder().setLaunchSingleTop(true).build()
+        )
+
+        // immediate third navigation with singleTop without executing previous transactions
+        navController.navigate(
+            R.id.testDialog_fragment,
+            null,
+            NavOptions.Builder().setLaunchSingleTop(true).build()
+        )
+
+        assertThat(navController.currentBackStackEntry?.destination?.id)
+            .isEqualTo(R.id.testDialog_fragment)
+
+        val fm = supportFragmentManager.findFragmentById(R.id.nav_host)?.childFragmentManager
+        fm?.executePendingTransactions()
+
+        assertThat(navController.currentBackStackEntry?.destination?.id)
+            .isEqualTo(R.id.testDialog_fragment)
+        val replacementFragment = fm?.findFragmentByTag(navController.currentBackStackEntry?.id)
+            as? TestDialogFragment
+
+        assertThat(replacementFragment!!.dialogs.first().isShowing).isTrue()
+
+        // ensure previous fragments are dismissed and backStacks are in sync
+        assertThat(navigator.backStack.value.size).isEqualTo(1)
+        assertThat(fm.fragments.size).isEqualTo(2) // start + dialog fragment
+    }
+
+    @Test
+    fun dialogFragmentNavigate_partiallyImmediateSingleTop() = withNavigationActivity {
+        val navigator =
+            navController.navigatorProvider.getNavigator(DialogFragmentNavigator::class.java)
+
+        // first navigation
+        navController.navigate(R.id.testDialog_fragment)
+
+        val fm = supportFragmentManager.findFragmentById(R.id.nav_host)?.childFragmentManager
+        fm?.executePendingTransactions()
+
+        // second navigation with singleTop
+        navController.navigate(
+            R.id.testDialog_fragment,
+            null,
+            NavOptions.Builder().setLaunchSingleTop(true).build()
+        )
+
+        // immediate third navigation with singleTop without executing previous singleTop
+        navController.navigate(
+            R.id.testDialog_fragment,
+            null,
+            NavOptions.Builder().setLaunchSingleTop(true).build()
+        )
+
+        fm?.executePendingTransactions()
+
+        assertThat(navController.currentBackStackEntry?.destination?.id)
+            .isEqualTo(R.id.testDialog_fragment)
+        val replacementFragment = fm?.findFragmentByTag(navController.currentBackStackEntry?.id)
+            as? TestDialogFragment
+
+        assertThat(replacementFragment!!.dialogs.first().isShowing).isTrue()
+
+        // ensure previous fragments are dismissed and backStacks are in sync
+        assertThat(navigator.backStack.value.size).isEqualTo(1)
+        assertThat(fm.fragments.size).isEqualTo(2) // start + last dialog fragment
+    }
+
+    private fun withNavigationActivity(
+        block: NavigationActivity.() -> Unit
+    ) {
         with(ActivityScenario.launch(NavigationActivity::class.java)) {
             withActivity {
-                navController.navigate(R.id.empty_fragment)
-
-                val fm =
-                    supportFragmentManager.findFragmentById(R.id.nav_host)?.childFragmentManager
-                fm?.executePendingTransactions()
-                val fragment = fm?.findFragmentById(R.id.nav_host)
-
-                navController.navigate(
-                    R.id.empty_fragment,
-                    null,
-                    NavOptions.Builder().setLaunchSingleTop(true).build()
-                )
-
-                fm?.executePendingTransactions()
-
-                val replacementFragment = fm?.findFragmentById(R.id.nav_host)
-
-                assertWithMessage("Replacement should be a new instance")
-                    .that(replacementFragment)
-                    .isNotSameInstanceAs(fragment)
+                this.block()
             }
         }
     }
+}
+
+class TestDialogFragment : DialogFragment() {
+    val dialogs = mutableListOf<Dialog>()
+
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        val dialog = super.onCreateDialog(savedInstanceState)
+        dialogs.add(dialog)
+        return dialog
+    }
 }
\ No newline at end of file
diff --git a/navigation/navigation-fragment/src/androidTest/res/navigation/nav_simple.xml b/navigation/navigation-fragment/src/androidTest/res/navigation/nav_simple.xml
index 82681b8..a732a62 100644
--- a/navigation/navigation-fragment/src/androidTest/res/navigation/nav_simple.xml
+++ b/navigation/navigation-fragment/src/androidTest/res/navigation/nav_simple.xml
@@ -31,4 +31,7 @@
     <dialog
         android:id="@+id/dialog_fragment"
         android:name="androidx.navigation.fragment.EmptyDialogFragment"/>
+    <dialog
+        android:id="@+id/testDialog_fragment"
+        android:name="androidx.navigation.fragment.TestDialogFragment"/>
 </navigation>
\ No newline at end of file
diff --git a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/DialogFragmentNavigator.kt b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/DialogFragmentNavigator.kt
index 8ab8cc8..e8ab456 100644
--- a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/DialogFragmentNavigator.kt
+++ b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/DialogFragmentNavigator.kt
@@ -83,6 +83,11 @@
     internal val backStack: StateFlow<List<NavBackStackEntry>>
         get() = state.backStack
 
+    /**
+     * Stores DialogFragments that have been created but pendingTransaction.
+     */
+    private val transitioningFragments: MutableMap<String, DialogFragment> = mutableMapOf()
+
     override fun popBackStack(popUpTo: NavBackStackEntry, savedState: Boolean) {
         if (fragmentManager.isStateSaved) {
             Log.i(
@@ -129,6 +134,35 @@
     private fun navigate(
         entry: NavBackStackEntry
     ) {
+        val dialogFragment = createDialogFragment(entry)
+        dialogFragment.show(fragmentManager, entry.id)
+        state.push(entry)
+    }
+
+    override fun onLaunchSingleTop(backStackEntry: NavBackStackEntry) {
+        if (fragmentManager.isStateSaved) {
+            Log.i(
+                TAG,
+                "Ignoring onLaunchSingleTop() call: FragmentManager has already saved its state"
+            )
+            return
+        }
+
+        // Ensure previous fragment is dismissed. If it is in transition, we have to dismiss it
+        // here before its value with same key (entry.id) gets replaced by new fragment.
+        val oldFragment = transitioningFragments[backStackEntry.id]
+            ?: fragmentManager.findFragmentByTag(backStackEntry.id) as? DialogFragment
+        if (oldFragment != null) {
+            oldFragment.lifecycle.removeObserver(observer)
+            oldFragment.dismiss()
+        }
+
+        val newFragment = createDialogFragment(backStackEntry)
+        newFragment.show(fragmentManager, backStackEntry.id)
+        state.onLaunchSingleTop(backStackEntry)
+    }
+
+    private fun createDialogFragment(entry: NavBackStackEntry): DialogFragment {
         val destination = entry.destination as Destination
         var className = destination.className
         if (className[0] == '.') {
@@ -143,8 +177,12 @@
         val dialogFragment = frag as DialogFragment
         dialogFragment.arguments = entry.arguments
         dialogFragment.lifecycle.addObserver(observer)
-        dialogFragment.show(fragmentManager, entry.id)
-        state.push(entry)
+        // For singleTop navigations, this will overwrite existing transitioning fragments with
+        // the same `entry.id`. This is fine because before the singleTop DialogFragment
+        // is recreated and replaces the old record inside transitioningFragments, we would
+        // have already dismissed the existing (old) fragment.
+        transitioningFragments[entry.id] = dialogFragment
+        return dialogFragment
     }
 
     override fun onAttach(state: NavigatorState) {
@@ -160,6 +198,7 @@
             if (needToAddObserver) {
                 childFragment.lifecycle.addObserver(observer)
             }
+            transitioningFragments.remove(childFragment.tag)
         }
     }
 
diff --git a/navigation/navigation-runtime/build.gradle b/navigation/navigation-runtime/build.gradle
index a7e476e..06b831b 100644
--- a/navigation/navigation-runtime/build.gradle
+++ b/navigation/navigation-runtime/build.gradle
@@ -46,6 +46,7 @@
     androidTestImplementation(libs.mockitoCore, excludes.bytebuddy)
     androidTestImplementation(libs.dexmakerMockito, excludes.bytebuddy)
     androidTestImplementation(libs.kotlinStdlib)
+    androidTestImplementation(libs.kotlinTest)
     androidTestImplementation(libs.multidex)
 
     lintPublish(project(':navigation:navigation-runtime-lint'))
diff --git a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
index 33fa054..49c2ce6 100644
--- a/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
+++ b/navigation/navigation-runtime/src/androidTest/java/androidx/navigation/NavControllerRouteTest.kt
@@ -49,6 +49,7 @@
 import androidx.testutils.test
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
+import kotlin.test.assertFailsWith
 import org.hamcrest.CoreMatchers.allOf
 import org.hamcrest.CoreMatchers.not
 import org.hamcrest.Matchers
@@ -345,6 +346,215 @@
 
     @UiThreadTest
     @Test
+    fun testGetBackStackEntryWithExactRoute() {
+        val navController = createNavController()
+        navController.graph =
+            navController.createGraph(route = "nav_root", startDestination = "start_test") {
+                test("start_test")
+                test("second_test/{arg}") {
+                    argument("arg") { type = NavType.StringType }
+                }
+            }
+
+        // first nav with arg filed in
+        val deepLink = Uri.parse("android-app://androidx.navigation/second_test/13")
+        navController.navigate(deepLink)
+
+        // second nav with arg filled in
+        val deepLink2 = Uri.parse("android-app://androidx.navigation/second_test/18")
+        navController.navigate(deepLink2)
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        // ["start_test", "second_test/13", "second_test/18"]
+        assertThat(navigator.backStack.size).isEqualTo(3)
+
+        val entry1 = navController.getBackStackEntry("second_test/13")
+        assertThat(entry1).isEqualTo(navigator.backStack[1])
+
+        val entry2 = navController.getBackStackEntry("second_test/18")
+        assertThat(entry2).isEqualTo(navigator.backStack[2])
+    }
+
+    @UiThreadTest
+    @Test
+    fun testGetBackStackEntryWithExactRoute_multiArgs() {
+        val navController = createNavController()
+        navController.graph =
+            navController.createGraph(route = "nav_root", startDestination = "start_test") {
+                test("start_test")
+                test("second_test/{arg}/{arg2}") {
+                    argument("arg") { type = NavType.StringType }
+                    argument("arg2") { type = NavType.StringType }
+                }
+            }
+
+        // navigate with both args filed in
+        val deepLink = Uri.parse("android-app://androidx.navigation/second_test/13/18")
+        navController.navigate(deepLink)
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        // ["start_test", "second_test/13/18"]
+        assertThat(navigator.backStack.size).isEqualTo(2)
+
+        val entry1 = navController.getBackStackEntry("second_test/13/18")
+        assertThat(entry1).isEqualTo(navigator.backStack[1])
+    }
+
+    @UiThreadTest
+    @Test
+    fun testGetBackStackEntryWithPartialExactRoute() {
+        val navController = createNavController()
+        navController.graph =
+            navController.createGraph(route = "nav_root", startDestination = "start_test") {
+                test("start_test")
+                test("second_test/{arg}/{arg2}") {
+                    argument("arg") { type = NavType.StringType }
+                    argument("arg2") { type = NavType.StringType }
+                }
+            }
+
+        // navigate with args partially filed in
+        val deepLink = Uri.parse("android-app://androidx.navigation/second_test/13/{arg2}")
+        navController.navigate(deepLink)
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        // ["start_test", "second_test/13/{arg2}"]
+        assertThat(navigator.backStack.size).isEqualTo(2)
+        // routes with partially filled in args will also match as long as the args
+        // are filled in the exact same way
+        val entry1 = navController.getBackStackEntry("second_test/13/{arg2}")
+        assertThat(entry1).isEqualTo(navigator.backStack[1])
+    }
+
+    @UiThreadTest
+    @Test
+    fun testGetBackStackEntryWithIncorrectExactRoute() {
+        val navController = createNavController()
+        navController.graph =
+            navController.createGraph(route = "nav_root", startDestination = "start_test") {
+                test("start_test")
+                test("second_test/{arg}") {
+                    argument("arg") { type = NavType.StringType }
+                }
+            }
+
+        // navigate with arg filed in
+        val deepLink = Uri.parse("android-app://androidx.navigation/second_test/13")
+        navController.navigate(deepLink)
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        // ["start_test", "second_test/13"]
+        assertThat(navigator.backStack.size).isEqualTo(2)
+
+        // route "second_test/18" should not match with any entries in backstack since we never
+        // navigated with args "18"
+        val route = "second_test/18"
+        val exception = assertFailsWith<IllegalArgumentException> {
+            navController.getBackStackEntry(route)
+        }
+        assertThat(exception.message).isEqualTo(
+            "No destination with route $route is on the NavController's " +
+                "back stack. The current destination is ${navController.currentDestination}"
+        )
+    }
+
+    @UiThreadTest
+    @Test
+    fun testGetBackStackEntryWithIncorrectExactRoute_multiArgs() {
+        val navController = createNavController()
+        navController.graph =
+            navController.createGraph(route = "nav_root", startDestination = "start_test") {
+                test("start_test")
+                test("second_test/{arg}/{arg2}") {
+                    argument("arg") { type = NavType.StringType }
+                    argument("arg2") { type = NavType.StringType }
+                }
+            }
+
+        // navigate with args partially filed in
+        val deepLink = Uri.parse("android-app://androidx.navigation/second_test/13/18")
+        navController.navigate(deepLink)
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        // ["start_test", "second_test/13/18"]
+        assertThat(navigator.backStack.size).isEqualTo(2)
+
+        // route "second_test/13/13" should not match with any entries in backstack
+        val route = "second_test/13/19"
+        val exception = assertFailsWith<IllegalArgumentException> {
+            navController.getBackStackEntry(route)
+        }
+        assertThat(exception.message).isEqualTo(
+            "No destination with route $route is on the NavController's " +
+                "back stack. The current destination is ${navController.currentDestination}"
+        )
+    }
+
+    @UiThreadTest
+    @Test
+    fun testGetBackStackEntryWithAdditionalPartialArgs() {
+        val navController = createNavController()
+        navController.graph =
+            navController.createGraph(route = "nav_root", startDestination = "start_test") {
+                test("start_test")
+                test("second_test/{arg}/{arg2}") {
+                    argument("arg") { type = NavType.StringType }
+                    argument("arg2") { type = NavType.StringType }
+                }
+            }
+
+        // navigate with args partially filed in
+        val deepLink = Uri.parse("android-app://androidx.navigation/second_test/13/{arg2}")
+        navController.navigate(deepLink)
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        // ["start_test", "second_test/13/{arg2}"]
+        assertThat(navigator.backStack.size).isEqualTo(2)
+
+        // route with additional arg "14" should not match
+        val route = "second_test/13/14"
+        val exception = assertFailsWith<IllegalArgumentException> {
+            navController.getBackStackEntry(route)
+        }
+        assertThat(exception.message).isEqualTo(
+            "No destination with route $route is on the NavController's " +
+                "back stack. The current destination is ${navController.currentDestination}"
+        )
+    }
+
+    @UiThreadTest
+    @Test
+    fun testGetBackStackEntryWithMissingPartialArgs() {
+        val navController = createNavController()
+        navController.graph =
+            navController.createGraph(route = "nav_root", startDestination = "start_test") {
+                test("start_test")
+                test("second_test/{arg}/{arg2}") {
+                    argument("arg") { type = NavType.StringType }
+                    argument("arg2") { type = NavType.StringType }
+                }
+            }
+
+        val deepLink = Uri.parse("android-app://androidx.navigation/second_test/13/18")
+        navController.navigate(deepLink)
+
+        val navigator = navController.navigatorProvider.getNavigator(TestNavigator::class.java)
+        // ["start_test", "second_test/13/18"]
+        assertThat(navigator.backStack.size).isEqualTo(2)
+
+        // route missing arg "18" should not match
+        val route = "second_test/13/{arg2}"
+        val exception = assertFailsWith<IllegalArgumentException> {
+            navController.getBackStackEntry(route)
+        }
+        assertThat(exception.message).isEqualTo(
+            "No destination with route $route is on the NavController's " +
+                "back stack. The current destination is ${navController.currentDestination}"
+        )
+    }
+
+    @UiThreadTest
+    @Test
     fun testNavigateViaDeepLinkDefaultArgs() {
         val navController = createNavController()
         navController.graph = nav_simple_route_graph
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
index 4a1ba0f..401c387 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/NavController.kt
@@ -2281,7 +2281,7 @@
      */
     public fun getBackStackEntry(route: String): NavBackStackEntry {
         val lastFromBackStack: NavBackStackEntry? = backQueue.lastOrNull { entry ->
-            entry.destination.route == route
+            entry.destination.hasRoute(route, entry.arguments, _graph!!)
         }
         requireNotNull(lastFromBackStack) {
             "No destination with route $route is on the NavController's back stack. The " +
diff --git a/navigation/navigation-safe-args-gradle-plugin/src/test/kotlin/androidx/navigation/safeargs/gradle/BasePluginTest.kt b/navigation/navigation-safe-args-gradle-plugin/src/test/kotlin/androidx/navigation/safeargs/gradle/BasePluginTest.kt
index a046004..c3e20c9 100644
--- a/navigation/navigation-safe-args-gradle-plugin/src/test/kotlin/androidx/navigation/safeargs/gradle/BasePluginTest.kt
+++ b/navigation/navigation-safe-args-gradle-plugin/src/test/kotlin/androidx/navigation/safeargs/gradle/BasePluginTest.kt
@@ -142,11 +142,22 @@
             suffix = """
                 android {
                     namespace 'androidx.navigation.testapp'
+                    compileOptions {
+                        sourceCompatibility = JavaVersion.VERSION_1_8
+                        targetCompatibility = JavaVersion.VERSION_1_8
+                    }
                 }
                 dependencies {
                     implementation "${projectSetup.props.kotlinStblib}"
                     implementation "${projectSetup.props.navigationRuntime}"
                 }
+                tasks.withType(
+                    org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+                ).configureEach {
+                    kotlinOptions {
+                        jvmTarget = "1.8"
+                    }
+                }
             """.trimIndent()
         )
     }
diff --git a/navigation/navigation-safe-args-gradle-plugin/src/test/kotlin/androidx/navigation/safeargs/gradle/KotlinPluginTest.kt b/navigation/navigation-safe-args-gradle-plugin/src/test/kotlin/androidx/navigation/safeargs/gradle/KotlinPluginTest.kt
index a325ede..489c3d3 100644
--- a/navigation/navigation-safe-args-gradle-plugin/src/test/kotlin/androidx/navigation/safeargs/gradle/KotlinPluginTest.kt
+++ b/navigation/navigation-safe-args-gradle-plugin/src/test/kotlin/androidx/navigation/safeargs/gradle/KotlinPluginTest.kt
@@ -53,11 +53,22 @@
                             applicationIdSuffix ".foo"
                         }
                     }
+                    compileOptions {
+                        sourceCompatibility = JavaVersion.VERSION_1_8
+                        targetCompatibility = JavaVersion.VERSION_1_8
+                    }
                 }
                 dependencies {
                     implementation "${projectSetup.props.kotlinStblib}"
                     implementation "${projectSetup.props.navigationRuntime}"
                 }
+                tasks.withType(
+                    org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+                ).configureEach {
+                    kotlinOptions {
+                        jvmTarget = "1.8"
+                    }
+                }
             """.trimIndent()
         )
         runGradle("assembleDebug").assertSuccessfulTask("assembleDebug")
diff --git a/paging/paging-common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt b/paging/paging-common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
index b88a6bbd..556f8ba 100644
--- a/paging/paging-common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
+++ b/paging/paging-common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
@@ -34,8 +34,8 @@
 import kotlinx.coroutines.channels.BufferOverflow.DROP_OLDEST
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asSharedFlow
-import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.withContext
 import kotlinx.coroutines.yield
 
@@ -338,8 +338,8 @@
      *
      * @sample androidx.paging.samples.loadStateFlowSample
      */
-    public val loadStateFlow: Flow<CombinedLoadStates> =
-        combinedLoadStatesCollection.stateFlow.filterNotNull()
+    public val loadStateFlow: StateFlow<CombinedLoadStates?> =
+        combinedLoadStatesCollection.stateFlow
 
     private val _onPagesUpdatedFlow: MutableSharedFlow<Unit> = MutableSharedFlow(
         replay = 0,
diff --git a/paging/paging-common/src/test/kotlin/androidx/paging/PagingDataDifferTest.kt b/paging/paging-common/src/test/kotlin/androidx/paging/PagingDataDifferTest.kt
index 0985d16..4740924 100644
--- a/paging/paging-common/src/test/kotlin/androidx/paging/PagingDataDifferTest.kt
+++ b/paging/paging-common/src/test/kotlin/androidx/paging/PagingDataDifferTest.kt
@@ -41,6 +41,7 @@
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.consumeAsFlow
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOf
@@ -217,7 +218,7 @@
         pageEventCh.trySend(
             localLoadStateUpdate(refreshLocal = Loading)
         )
-        assertThat(differ.loadStateFlow.first()).isEqualTo(
+        assertThat(differ.nonNullLoadStateFlow.first()).isEqualTo(
             localLoadStatesOf(refreshLocal = Loading)
         )
 
@@ -999,7 +1000,7 @@
         var combinedLoadStates: CombinedLoadStates? = null
         var itemCount = -1
         val loadStateJob = launch {
-            differ.loadStateFlow.collect {
+            differ.nonNullLoadStateFlow.collect {
                 combinedLoadStates = it
                 itemCount = differ.size
             }
@@ -1049,7 +1050,7 @@
         // Should not immediately emit without a real value to a new collector.
         val combinedLoadStates = mutableListOf<CombinedLoadStates>()
         val loadStateJob = launch {
-            differ.loadStateFlow.collect {
+            differ.nonNullLoadStateFlow.collect {
                 combinedLoadStates.add(it)
             }
         }
@@ -1074,7 +1075,7 @@
         // Should emit real values to new collectors immediately
         val newCombinedLoadStates = mutableListOf<CombinedLoadStates>()
         val newLoadStateJob = launch {
-            differ.loadStateFlow.collect {
+            differ.nonNullLoadStateFlow.collect {
                 newCombinedLoadStates.add(it)
             }
         }
@@ -1096,7 +1097,7 @@
         // Should not immediately emit without a real value to a new collector.
         val combinedLoadStates = mutableListOf<CombinedLoadStates>()
         val loadStateJob = launch {
-            differ.loadStateFlow.collect {
+            differ.nonNullLoadStateFlow.collect {
                 combinedLoadStates.add(it)
             }
         }
@@ -1144,7 +1145,7 @@
         // New observers should receive the previous state.
         val newCombinedLoadStates = mutableListOf<CombinedLoadStates>()
         val newLoadStateJob = launch {
-            differ.loadStateFlow.collect {
+            differ.nonNullLoadStateFlow.collect {
                 newCombinedLoadStates.add(it)
             }
         }
@@ -1173,7 +1174,7 @@
         // Should not immediately emit without a real value to a new collector.
         val combinedLoadStates = mutableListOf<CombinedLoadStates>()
         val loadStateJob = launch {
-            differ.loadStateFlow.collect {
+            differ.nonNullLoadStateFlow.collect {
                 combinedLoadStates.add(it)
             }
         }
@@ -1221,7 +1222,7 @@
         // New observers should receive the previous state.
         val newCombinedLoadStates = mutableListOf<CombinedLoadStates>()
         val newLoadStateJob = launch {
-            differ.loadStateFlow.collect {
+            differ.nonNullLoadStateFlow.collect {
                 newCombinedLoadStates.add(it)
             }
         }
@@ -1249,7 +1250,7 @@
 
         val combinedLoadStates = mutableListOf<CombinedLoadStates>()
         backgroundScope.launch {
-            differ.loadStateFlow.collect {
+            differ.nonNullLoadStateFlow.collect {
                 combinedLoadStates.add(it)
             }
         }
@@ -2194,6 +2195,8 @@
 
     private val _localLoadStates = mutableListOf<CombinedLoadStates>()
 
+    val nonNullLoadStateFlow = loadStateFlow.filterNotNull()
+
     fun newCombinedLoadStates(): List<CombinedLoadStates?> {
         val newCombinedLoadStates = _localLoadStates.toList()
         _localLoadStates.clear()
@@ -2202,7 +2205,7 @@
 
     fun collectLoadStates(): Job {
         return coroutineScope.launch {
-            loadStateFlow.collect { combinedLoadStates ->
+            nonNullLoadStateFlow.collect { combinedLoadStates ->
                 _localLoadStates.add(combinedLoadStates)
             }
         }
diff --git a/paging/paging-compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt b/paging/paging-compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt
index 49e029e..9095b67 100644
--- a/paging/paging-compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt
+++ b/paging/paging-compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt
@@ -99,6 +99,37 @@
     }
 
     @Test
+    fun lazyPagingLoadStateAfterRefresh() {
+        val pager = createPager()
+        val loadStates: MutableList<CombinedLoadStates> = mutableListOf()
+
+        lateinit var lazyPagingItems: LazyPagingItems<Int>
+        rule.setContent {
+            lazyPagingItems = pager.flow.collectAsLazyPagingItems()
+            loadStates.add(lazyPagingItems.loadState)
+        }
+
+        // we only want loadStates after manual refresh
+        loadStates.clear()
+        lazyPagingItems.refresh()
+        rule.waitForIdle()
+
+        assertThat(loadStates).isNotEmpty()
+        val expected = CombinedLoadStates(
+            refresh = LoadState.Loading,
+            prepend = LoadState.NotLoading(false),
+            append = LoadState.NotLoading(false),
+            source = LoadStates(
+                LoadState.Loading,
+                LoadState.NotLoading(false),
+                LoadState.NotLoading(false)
+            ),
+            mediator = null
+        )
+        assertThat(loadStates.first()).isEqualTo(expected)
+    }
+
+    @Test
     fun lazyPagingColumnShowsItems() {
         val pager = createPager()
         rule.setContent {
diff --git a/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt b/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
index 30d786d..7ba0385 100644
--- a/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
+++ b/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
@@ -45,6 +45,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.withContext
 
 /**
@@ -179,17 +180,18 @@
      * A [CombinedLoadStates] object which represents the current loading state.
      */
     public var loadState: CombinedLoadStates by mutableStateOf(
-        CombinedLoadStates(
-            refresh = InitialLoadStates.refresh,
-            prepend = InitialLoadStates.prepend,
-            append = InitialLoadStates.append,
-            source = InitialLoadStates
+        pagingDataDiffer.loadStateFlow.value
+            ?: CombinedLoadStates(
+                refresh = InitialLoadStates.refresh,
+                prepend = InitialLoadStates.prepend,
+                append = InitialLoadStates.append,
+                source = InitialLoadStates
+            )
         )
-    )
         private set
 
     internal suspend fun collectLoadState() {
-        pagingDataDiffer.loadStateFlow.collect {
+        pagingDataDiffer.loadStateFlow.filterNotNull().collect {
             loadState = it
         }
     }
diff --git a/paging/paging-runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt b/paging/paging-runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
index 287aeef..81bce0e 100644
--- a/paging/paging-runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
+++ b/paging/paging-runtime/src/main/java/androidx/paging/AsyncPagingDataDiffer.kt
@@ -29,6 +29,7 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
@@ -340,7 +341,7 @@
      *
      * @sample androidx.paging.samples.loadStateFlowSample
      */
-    val loadStateFlow: Flow<CombinedLoadStates> = differBase.loadStateFlow
+    val loadStateFlow: Flow<CombinedLoadStates> = differBase.loadStateFlow.filterNotNull()
 
     /**
      * A hot [Flow] that emits after the pages presented to the UI are updated, even if the
diff --git a/paging/paging-testing/api/current.txt b/paging/paging-testing/api/current.txt
index bcde83a..b48b6de 100644
--- a/paging/paging-testing/api/current.txt
+++ b/paging/paging-testing/api/current.txt
@@ -9,6 +9,14 @@
     method public suspend Object? appendScrollWhile(kotlin.jvm.functions.Function1<Value,java.lang.Boolean> predicate, kotlin.coroutines.Continuation<kotlin.Unit>);
     method public suspend Object? prependScrollWhile(kotlin.jvm.functions.Function1<Value,java.lang.Boolean> predicate, kotlin.coroutines.Continuation<kotlin.Unit>);
     method public suspend Object? refresh(kotlin.coroutines.Continuation<kotlin.Unit>);
+    method public suspend Object? scrollTo(int index, optional androidx.paging.testing.SnapshotLoader.ScrollBehavior scrollBehavior, optional kotlin.coroutines.Continuation<kotlin.Unit>);
+  }
+
+  public enum SnapshotLoader.ScrollBehavior {
+    method public static androidx.paging.testing.SnapshotLoader.ScrollBehavior valueOf(String name) throws java.lang.IllegalArgumentException;
+    method public static androidx.paging.testing.SnapshotLoader.ScrollBehavior[] values();
+    enum_constant public static final androidx.paging.testing.SnapshotLoader.ScrollBehavior ScrollIntoPlaceholders;
+    enum_constant public static final androidx.paging.testing.SnapshotLoader.ScrollBehavior WaitForPlaceholdersToLoad;
   }
 
   public final class StaticListPagingSourceFactoryKt {
diff --git a/paging/paging-testing/api/public_plus_experimental_current.txt b/paging/paging-testing/api/public_plus_experimental_current.txt
index bcde83a..b48b6de 100644
--- a/paging/paging-testing/api/public_plus_experimental_current.txt
+++ b/paging/paging-testing/api/public_plus_experimental_current.txt
@@ -9,6 +9,14 @@
     method public suspend Object? appendScrollWhile(kotlin.jvm.functions.Function1<Value,java.lang.Boolean> predicate, kotlin.coroutines.Continuation<kotlin.Unit>);
     method public suspend Object? prependScrollWhile(kotlin.jvm.functions.Function1<Value,java.lang.Boolean> predicate, kotlin.coroutines.Continuation<kotlin.Unit>);
     method public suspend Object? refresh(kotlin.coroutines.Continuation<kotlin.Unit>);
+    method public suspend Object? scrollTo(int index, optional androidx.paging.testing.SnapshotLoader.ScrollBehavior scrollBehavior, optional kotlin.coroutines.Continuation<kotlin.Unit>);
+  }
+
+  public enum SnapshotLoader.ScrollBehavior {
+    method public static androidx.paging.testing.SnapshotLoader.ScrollBehavior valueOf(String name) throws java.lang.IllegalArgumentException;
+    method public static androidx.paging.testing.SnapshotLoader.ScrollBehavior[] values();
+    enum_constant public static final androidx.paging.testing.SnapshotLoader.ScrollBehavior ScrollIntoPlaceholders;
+    enum_constant public static final androidx.paging.testing.SnapshotLoader.ScrollBehavior WaitForPlaceholdersToLoad;
   }
 
   public final class StaticListPagingSourceFactoryKt {
diff --git a/paging/paging-testing/api/restricted_current.txt b/paging/paging-testing/api/restricted_current.txt
index bcde83a..b48b6de 100644
--- a/paging/paging-testing/api/restricted_current.txt
+++ b/paging/paging-testing/api/restricted_current.txt
@@ -9,6 +9,14 @@
     method public suspend Object? appendScrollWhile(kotlin.jvm.functions.Function1<Value,java.lang.Boolean> predicate, kotlin.coroutines.Continuation<kotlin.Unit>);
     method public suspend Object? prependScrollWhile(kotlin.jvm.functions.Function1<Value,java.lang.Boolean> predicate, kotlin.coroutines.Continuation<kotlin.Unit>);
     method public suspend Object? refresh(kotlin.coroutines.Continuation<kotlin.Unit>);
+    method public suspend Object? scrollTo(int index, optional androidx.paging.testing.SnapshotLoader.ScrollBehavior scrollBehavior, optional kotlin.coroutines.Continuation<kotlin.Unit>);
+  }
+
+  public enum SnapshotLoader.ScrollBehavior {
+    method public static androidx.paging.testing.SnapshotLoader.ScrollBehavior valueOf(String name) throws java.lang.IllegalArgumentException;
+    method public static androidx.paging.testing.SnapshotLoader.ScrollBehavior[] values();
+    enum_constant public static final androidx.paging.testing.SnapshotLoader.ScrollBehavior ScrollIntoPlaceholders;
+    enum_constant public static final androidx.paging.testing.SnapshotLoader.ScrollBehavior WaitForPlaceholdersToLoad;
   }
 
   public final class StaticListPagingSourceFactoryKt {
diff --git a/paging/paging-testing/src/main/java/androidx/paging/testing/PagerFlowSnapshot.kt b/paging/paging-testing/src/main/java/androidx/paging/testing/PagerFlowSnapshot.kt
index 0f96639..5cdfe07 100644
--- a/paging/paging-testing/src/main/java/androidx/paging/testing/PagerFlowSnapshot.kt
+++ b/paging/paging-testing/src/main/java/androidx/paging/testing/PagerFlowSnapshot.kt
@@ -32,6 +32,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.firstOrNull
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
@@ -141,7 +142,7 @@
  * the aggregated LoadStates can reflect `NotLoading` when source states are `Loading`.
  */
 internal suspend fun <Value : Any> PagingDataDiffer<Value>.awaitNotLoading() {
-    loadStateFlow.filter {
+    loadStateFlow.filterNotNull().filter {
         it.source.isIdle() && it.mediator?.isIdle() ?: true
     }.firstOrNull()
 }
diff --git a/paging/paging-testing/src/main/java/androidx/paging/testing/SnapshotLoader.kt b/paging/paging-testing/src/main/java/androidx/paging/testing/SnapshotLoader.kt
index e4f73ac..a65cc1b 100644
--- a/paging/paging-testing/src/main/java/androidx/paging/testing/SnapshotLoader.kt
+++ b/paging/paging-testing/src/main/java/androidx/paging/testing/SnapshotLoader.kt
@@ -123,6 +123,71 @@
     }
 
     /**
+     * Imitates scrolling from current index to the target index.
+     *
+     * The scroll direction (prepend or append) is dependent on current index and target index. In
+     * general, scrolling to a smaller index triggers [PREPEND] while scrolling to a larger
+     * index triggers [APPEND].
+     *
+     * @param [index] The target index to scroll to
+     *
+     * @param [scrollBehavior] The default scroll behavior is
+     * [ScrollBehavior.WaitForPlaceholdersToLoad]. See [ScrollBehavior] for all scroll types.
+     */
+    public suspend fun scrollTo(
+        index: Int,
+        scrollBehavior: ScrollBehavior = ScrollBehavior.WaitForPlaceholdersToLoad
+    ): @JvmSuppressWildcards Unit {
+        differ.awaitNotLoading()
+        appendOrPrependScrollTo(index, scrollBehavior)
+        differ.awaitNotLoading()
+    }
+
+    /**
+     * Scrolls from current index to targeted [index].
+     *
+     * Internally this method scrolls until it fulfills requested index
+     * differential (Math.abs(requested index - current index)) rather than scrolling
+     * to the exact requested index. This is because item indices can shift depending on scroll
+     * direction and placeholders. Therefore we try to fulfill the expected amount of scrolling
+     * rather than the actual requested index.
+     */
+    private suspend fun appendOrPrependScrollTo(
+        index: Int,
+        scrollBehavior: ScrollBehavior,
+    ) {
+        val startIndex = generations.value.lastAccessedIndex.get()
+        val loadType = if (startIndex > index) LoadType.PREPEND else LoadType.APPEND
+        when (loadType) {
+            LoadType.PREPEND -> prependScrollTo(index, startIndex, scrollBehavior)
+            LoadType.APPEND -> {
+                // TODO
+            }
+        }
+    }
+
+    private suspend fun prependScrollTo(
+        index: Int,
+        startIndex: Int,
+        scrollBehavior: ScrollBehavior
+    ) {
+        val endIndex = maxOf(0, index)
+        val scrollCount = startIndex - endIndex
+        when (scrollBehavior) {
+            ScrollBehavior.WaitForPlaceholdersToLoad -> awaitScrollTo(LoadType.PREPEND, scrollCount)
+            ScrollBehavior.ScrollIntoPlaceholders -> {
+                // TODO
+            }
+        }
+    }
+
+    private suspend fun awaitScrollTo(loadType: LoadType, scrollCount: Int) {
+        repeat(scrollCount) {
+            awaitNextItem(loadType)
+        }
+    }
+
+    /**
      * Triggers load for next item, awaits for it to be loaded and returns the loaded item.
      *
      * It calculates the next load index based on loadType and this generation's
@@ -228,6 +293,29 @@
         PREPEND,
         APPEND
     }
+
+    /**
+     * Determines whether the fake scroll will wait for asynchronous data to be loaded in or not.
+     *
+     * @see scrollTo
+     */
+    public enum class ScrollBehavior {
+        /**
+         * Imitates slow scrolls by waiting for item to be loaded in before triggering
+         * load on next item. A scroll with this behavior will return all available data
+         * that has been scrolled through.
+         */
+        ScrollIntoPlaceholders,
+
+        /**
+         * Imitates fast scrolling that will continue scrolling as data is being loaded in
+         * asynchronously. A scroll with this behavior will return only the data that has been
+         * loaded in by the time scrolling ends. This mode can also be used to trigger paging
+         * jumps if the number of placeholders scrolled through is larger than
+         * [PagingConfig.jumpThreshold].
+         */
+        WaitForPlaceholdersToLoad
+    }
 }
 
 internal data class Generation(
diff --git a/paging/paging-testing/src/test/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt b/paging/paging-testing/src/test/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt
index b65ce1a..8a4c15d 100644
--- a/paging/paging-testing/src/test/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt
+++ b/paging/paging-testing/src/test/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt
@@ -19,6 +19,7 @@
 import androidx.paging.Pager
 import androidx.paging.PagingConfig
 import androidx.paging.cachedIn
+import androidx.paging.testing.SnapshotLoader.ScrollBehavior
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
@@ -756,6 +757,172 @@
         }
     }
 
+    @Test
+    fun prependToAwait() {
+        val dataFlow = flowOf(List(100) { it })
+        val factory = dataFlow.asPagingSourceFactory(testScope.backgroundScope)
+        val pager = Pager(
+            config = CONFIG,
+            initialKey = 50,
+            pagingSourceFactory = factory,
+        )
+        testScope.runTest {
+            val snapshot = pager.flow.asSnapshot(this) {
+                scrollTo(42, ScrollBehavior.WaitForPlaceholdersToLoad)
+            }
+            // initial load [50-54]
+            // prefetched [47-49], [55-57]
+            // prepended [41-46]
+            // prefetched [38-40]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(
+                    38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57
+                )
+            )
+        }
+    }
+
+    @Test
+    fun consecutivePrependToAwait() {
+        val dataFlow = flowOf(List(100) { it })
+        val factory = dataFlow.asPagingSourceFactory(testScope.backgroundScope)
+        val pager = Pager(
+            config = CONFIG,
+            initialKey = 50,
+            pagingSourceFactory = factory,
+        )
+        testScope.runTest {
+            val snapshot = pager.flow.asSnapshot(this) {
+                scrollTo(42, ScrollBehavior.WaitForPlaceholdersToLoad)
+            }
+            // initial load [50-54]
+            // prefetched [47-49], [55-57]
+            // prepended [41-46]
+            // prefetched [38-40]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(
+                    38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57
+                )
+            )
+
+            val snapshot2 = pager.flow.asSnapshot(this) {
+                scrollTo(38, ScrollBehavior.WaitForPlaceholdersToLoad)
+            }
+            // prefetched [35-37]
+            assertThat(snapshot2).containsExactlyElementsIn(
+                listOf(
+                    35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
+                    51, 52, 53, 54, 55, 56, 57
+                )
+            )
+        }
+    }
+
+    @Test
+    fun prependToAwait_withoutPrefetch() {
+        val dataFlow = flowOf(List(100) { it })
+        val factory = dataFlow.asPagingSourceFactory(testScope.backgroundScope)
+        val pager = Pager(
+            config = CONFIG_NO_PREFETCH,
+            initialKey = 50,
+            pagingSourceFactory = factory,
+        )
+        testScope.runTest {
+            val snapshot = pager.flow.asSnapshot(this) {
+                scrollTo(42, ScrollBehavior.WaitForPlaceholdersToLoad)
+            }
+            // initial load [50-54]
+            // prepended [41-49]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54)
+            )
+        }
+    }
+
+    @Test
+    fun prependToAwait_withoutPlaceholders() {
+        val dataFlow = flowOf(List(100) { it })
+        val factory = dataFlow.asPagingSourceFactory(testScope.backgroundScope)
+        val pager = Pager(
+            config = CONFIG_NO_PLACEHOLDERS,
+            initialKey = 50,
+            pagingSourceFactory = factory,
+        ).flow.cachedIn(testScope.backgroundScope)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                // Without placeholders, first loaded page always starts at index[0]
+                scrollTo(0, ScrollBehavior.WaitForPlaceholdersToLoad)
+            }
+            // initial load [50-54]
+            // prefetched [47-49], [55-57]
+            // scrollTo prepended [44-46]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
+            )
+        }
+    }
+
+    @Test
+    fun consecutivePrependToAwait_withoutPlaceholders() {
+        val dataFlow = flowOf(List(100) { it })
+        val factory = dataFlow.asPagingSourceFactory(testScope.backgroundScope)
+        val pager = Pager(
+            config = CONFIG_NO_PLACEHOLDERS,
+            initialKey = 50,
+            pagingSourceFactory = factory,
+        ).flow.cachedIn(testScope.backgroundScope)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                // Without placeholders, first loaded page always starts at index[0]
+                scrollTo(0, ScrollBehavior.WaitForPlaceholdersToLoad)
+            }
+            // initial load [50-54]
+            // prefetched [47-49], [55-57]
+            // scrollTo prepended [44-46]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
+            )
+
+            val snapshot2 = pager.asSnapshot(this) {
+                // Without placeholders, first loaded page always starts at index[0]
+                scrollTo(0, ScrollBehavior.WaitForPlaceholdersToLoad)
+            }
+            // scrollTo prepended [41-43]
+            assertThat(snapshot2).containsExactlyElementsIn(
+                listOf(41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
+            )
+        }
+    }
+
+    @Test
+    fun prependToAwait_withoutPlaceholders_noPrefetchTriggered() {
+        val dataFlow = flowOf(List(100) { it })
+        val factory = dataFlow.asPagingSourceFactory(testScope.backgroundScope)
+        val pager = Pager(
+            config = PagingConfig(
+                pageSize = 4,
+                initialLoadSize = 8,
+                enablePlaceholders = false,
+                // a small prefetchDistance to prevent prefetch until we scroll to boundary
+                prefetchDistance = 1
+            ),
+            initialKey = 50,
+            pagingSourceFactory = factory,
+        ).flow.cachedIn(testScope.backgroundScope)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                // Without placeholders, first loaded page always starts at index[0]
+                scrollTo(0)
+            }
+            // initial load [50-57]
+            // no prefetch after initial load because it didn't hit prefetch distance
+            // scrollTo prepended [46-49]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
+            )
+        }
+    }
+
     val CONFIG = PagingConfig(
         pageSize = 3,
         initialLoadSize = 5,
diff --git a/playground-common/androidx-shared.properties b/playground-common/androidx-shared.properties
index 8ae78c0..6ae45af 100644
--- a/playground-common/androidx-shared.properties
+++ b/playground-common/androidx-shared.properties
@@ -70,7 +70,14 @@
 kotlin.mpp.stability.nowarn=true
 # b/227307216
 kotlin.mpp.absentAndroidTarget.nowarn=true
+# As of October 3 2022, AGP 7.4.0-alpha08 is higher than AGP 7.3
+# Presumably complains if using a non-stable AGP, which we are regularly doing to test pre-stable.
+kotlin.mpp.androidGradlePluginCompatibility.nowarn=true
 
 # Properties we often want to toggle
 # ksp.version.check=false
 # androidx.compose.multiplatformEnabled=true
+
+kotlin.mpp.androidSourceSetLayoutVersion=1
+
+
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/AbstractSdkProviderGenerator.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/AbstractSdkProviderGenerator.kt
index c9e8b12..6c070aa 100644
--- a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/AbstractSdkProviderGenerator.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/AbstractSdkProviderGenerator.kt
@@ -41,7 +41,7 @@
             return null
         }
         val packageName = api.getOnlyService().type.packageName
-        val className = "AbstractSandboxedSdkProvider"
+        val className = "AbstractSandboxedSdkProviderCompat"
         val classSpec =
             TypeSpec.classBuilder(className)
                 .superclass(superclassName)
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/CompatSdkProviderGenerator.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/CompatSdkProviderGenerator.kt
index 9934deb..00b4801 100644
--- a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/CompatSdkProviderGenerator.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/CompatSdkProviderGenerator.kt
@@ -23,7 +23,6 @@
 import com.squareup.kotlinpoet.ClassName
 import com.squareup.kotlinpoet.FunSpec
 import com.squareup.kotlinpoet.KModifier
-import com.squareup.kotlinpoet.MemberName
 
 /** SDK Provider generator that uses the SDK Runtime library to communicate with the sandbox. */
 internal class CompatSdkProviderGenerator(parsedApi: ParsedApi) :
@@ -31,14 +30,6 @@
     companion object {
         private val sandboxedSdkCompatClass =
             ClassName("androidx.privacysandbox.sdkruntime.core", "SandboxedSdkCompat")
-        private val sandboxedSdkCompatCreateMethod =
-            MemberName(
-                ClassName(
-                    sandboxedSdkCompatClass.packageName,
-                    sandboxedSdkCompatClass.simpleName,
-                    "Companion"
-                ), "create"
-            )
     }
 
     override val superclassName =
@@ -52,8 +43,8 @@
             "val sdk = ${createServiceFunctionName(api.getOnlyService())}(context!!)"
         )
         addStatement(
-            "return %M(%T(sdk))",
-            sandboxedSdkCompatCreateMethod,
+            "return %T(%T(sdk))",
+            sandboxedSdkCompatClass,
             api.getOnlyService().stubDelegateNameSpec()
         )
     }
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/FullFeaturedSdkTest.kt b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/FullFeaturedSdkTest.kt
index 71b9761..bb58b74 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/FullFeaturedSdkTest.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/FullFeaturedSdkTest.kt
@@ -34,11 +34,7 @@
         val inputSources = loadSourcesFromDirectory(inputTestDataDir)
         val expectedKotlinSources = loadSourcesFromDirectory(outputTestDataDir)
 
-        val result = compileWithPrivacySandboxKspCompiler(
-            inputSources,
-            platformStubs = PlatformStubs.API_33,
-            extraProcessorOptions = mapOf("skip_sdk_runtime_compat_library" to "true")
-        )
+        val result = compileWithPrivacySandboxKspCompiler(inputSources)
         assertThat(result).succeeds()
 
         val expectedAidlFilepath = listOf(
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/SdkWithPackagesTest.kt b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/SdkWithPackagesTest.kt
index 51b78ad..7928c89 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/SdkWithPackagesTest.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/SdkWithPackagesTest.kt
@@ -35,11 +35,7 @@
         val inputSources = loadSourcesFromDirectory(inputTestDataDir)
         val expectedKotlinSources = loadSourcesFromDirectory(outputTestDataDir)
 
-        val result = compileWithPrivacySandboxKspCompiler(
-            inputSources,
-            platformStubs = PlatformStubs.API_33,
-            extraProcessorOptions = mapOf("skip_sdk_runtime_compat_library" to "true")
-        )
+        val result = compileWithPrivacySandboxKspCompiler(inputSources)
         assertThat(result).succeeds()
 
         val expectedAidlFilepath = listOf(
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/TestUtils.kt b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/TestUtils.kt
index e6b6e3d..66b5d74 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/TestUtils.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/TestUtils.kt
@@ -62,12 +62,8 @@
         |import android.os.IBinder
         |
         |@Suppress("UNUSED_PARAMETER")
-        |sealed class SandboxedSdkCompat {
-        |    abstract fun getInterface(): IBinder?
-        |
-        |    companion object {
-        |        fun create(binder: IBinder): SandboxedSdkCompat = throw RuntimeException("Stub!")
-        |    }
+        |class SandboxedSdkCompat(sdkInterface: IBinder) {
+        |    fun getInterface(): IBinder? = throw RuntimeException("Stub!")
         |}
         |""".trimMargin()
     ),
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/SdkRuntimeLibrarySdkTest.kt b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/WithoutRuntimeLibrarySdkTest.kt
similarity index 76%
rename from privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/SdkRuntimeLibrarySdkTest.kt
rename to privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/WithoutRuntimeLibrarySdkTest.kt
index 1ef27b0..0c98829 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/SdkRuntimeLibrarySdkTest.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/WithoutRuntimeLibrarySdkTest.kt
@@ -24,20 +24,24 @@
 import org.junit.runners.JUnit4
 
 @RunWith(JUnit4::class)
-class SdkRuntimeLibrarySdkTest {
+class WithoutRuntimeLibrarySdkTest {
     @Test
     fun compileServiceInterface_ok() {
-        val inputTestDataDir = File("src/test/test-data/sdkruntimelibrarysdk/input")
-        val outputTestDataDir = File("src/test/test-data/sdkruntimelibrarysdk/output")
+        val inputTestDataDir = File("src/test/test-data/withoutruntimelibrarysdk/input")
+        val outputTestDataDir = File("src/test/test-data/withoutruntimelibrarysdk/output")
         val inputSources = loadSourcesFromDirectory(inputTestDataDir)
         val expectedKotlinSources = loadSourcesFromDirectory(outputTestDataDir)
 
-        val result = compileWithPrivacySandboxKspCompiler(inputSources)
+        val result = compileWithPrivacySandboxKspCompiler(
+            inputSources,
+            platformStubs = PlatformStubs.API_33,
+            extraProcessorOptions = mapOf("skip_sdk_runtime_compat_library" to "true")
+        )
         assertThat(result).succeeds()
 
         val expectedAidlFilepath = listOf(
             "com/mysdk/ICancellationSignal.java",
-            "com/mysdk/IBackwardsCompatibleSdk.java",
+            "com/mysdk/IWithoutRuntimeLibrarySdk.java",
             "com/mysdk/IStringTransactionCallback.java",
             "com/mysdk/ParcelableStackFrame.java",
             "com/mysdk/PrivacySandboxThrowableParcel.java",
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/AbstractSandboxedSdkProvider.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/AbstractSandboxedSdkProvider.kt
deleted file mode 100644
index 43c54ac..0000000
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/AbstractSandboxedSdkProvider.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.mysdk
-
-import android.app.sdksandbox.SandboxedSdk
-import android.app.sdksandbox.SandboxedSdkProvider
-import android.content.Context
-import android.os.Bundle
-import android.view.View
-import kotlin.Int
-
-public abstract class AbstractSandboxedSdkProvider : SandboxedSdkProvider() {
-  public override fun onLoadSdk(params: Bundle): SandboxedSdk {
-    val sdk = createMySdk(context!!)
-    return SandboxedSdk(MySdkStubDelegate(sdk))
-  }
-
-  public override fun getView(
-    windowContext: Context,
-    params: Bundle,
-    width: Int,
-    height: Int,
-  ): View {
-    TODO("Implement")
-  }
-
-  protected abstract fun createMySdk(context: Context): MySdk
-}
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/AbstractSandboxedSdkProviderCompat.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/AbstractSandboxedSdkProviderCompat.kt
new file mode 100644
index 0000000..afe24f8
--- /dev/null
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/fullfeaturedsdk/output/com/mysdk/AbstractSandboxedSdkProviderCompat.kt
@@ -0,0 +1,26 @@
+package com.mysdk
+
+import android.content.Context
+import android.os.Bundle
+import android.view.View
+import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
+import androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat
+import kotlin.Int
+
+public abstract class AbstractSandboxedSdkProviderCompat : SandboxedSdkProviderCompat() {
+  public override fun onLoadSdk(params: Bundle): SandboxedSdkCompat {
+    val sdk = createMySdk(context!!)
+    return SandboxedSdkCompat(MySdkStubDelegate(sdk))
+  }
+
+  public override fun getView(
+    windowContext: Context,
+    params: Bundle,
+    width: Int,
+    height: Int,
+  ): View {
+    TODO("Implement")
+  }
+
+  protected abstract fun createMySdk(context: Context): MySdk
+}
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkruntimelibrarysdk/output/com/mysdk/AbstractSandboxedSdkProvider.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkruntimelibrarysdk/output/com/mysdk/AbstractSandboxedSdkProvider.kt
deleted file mode 100644
index 5a0d588..0000000
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkruntimelibrarysdk/output/com/mysdk/AbstractSandboxedSdkProvider.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.mysdk
-
-import android.content.Context
-import android.os.Bundle
-import android.view.View
-import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
-import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat.Companion.create
-import androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat
-import kotlin.Int
-
-public abstract class AbstractSandboxedSdkProvider : SandboxedSdkProviderCompat() {
-  public override fun onLoadSdk(params: Bundle): SandboxedSdkCompat {
-    val sdk = createBackwardsCompatibleSdk(context!!)
-    return create(BackwardsCompatibleSdkStubDelegate(sdk))
-  }
-
-  public override fun getView(
-    windowContext: Context,
-    params: Bundle,
-    width: Int,
-    height: Int,
-  ): View {
-    TODO("Implement")
-  }
-
-  protected abstract fun createBackwardsCompatibleSdk(context: Context): BackwardsCompatibleSdk
-}
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkruntimelibrarysdk/output/com/mysdk/BackwardsCompatibleSdkFactory.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkruntimelibrarysdk/output/com/mysdk/BackwardsCompatibleSdkFactory.kt
deleted file mode 100644
index 63749650..0000000
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkruntimelibrarysdk/output/com/mysdk/BackwardsCompatibleSdkFactory.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.mysdk
-
-import android.os.IBinder
-import androidx.privacysandbox.tools.`internal`.GeneratedPublicApi
-
-@GeneratedPublicApi
-public object BackwardsCompatibleSdkFactory {
-    @Suppress("UNUSED_PARAMETER")
-    public fun wrapToBackwardsCompatibleSdk(binder: IBinder): BackwardsCompatibleSdk = throw
-            RuntimeException("Stub!")
-}
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/AbstractSandboxedSdkProvider.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/AbstractSandboxedSdkProvider.kt
deleted file mode 100644
index 43c54ac..0000000
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/AbstractSandboxedSdkProvider.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.mysdk
-
-import android.app.sdksandbox.SandboxedSdk
-import android.app.sdksandbox.SandboxedSdkProvider
-import android.content.Context
-import android.os.Bundle
-import android.view.View
-import kotlin.Int
-
-public abstract class AbstractSandboxedSdkProvider : SandboxedSdkProvider() {
-  public override fun onLoadSdk(params: Bundle): SandboxedSdk {
-    val sdk = createMySdk(context!!)
-    return SandboxedSdk(MySdkStubDelegate(sdk))
-  }
-
-  public override fun getView(
-    windowContext: Context,
-    params: Bundle,
-    width: Int,
-    height: Int,
-  ): View {
-    TODO("Implement")
-  }
-
-  protected abstract fun createMySdk(context: Context): MySdk
-}
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/AbstractSandboxedSdkProviderCompat.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/AbstractSandboxedSdkProviderCompat.kt
new file mode 100644
index 0000000..afe24f8
--- /dev/null
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkwithpackages/output/com/mysdk/AbstractSandboxedSdkProviderCompat.kt
@@ -0,0 +1,26 @@
+package com.mysdk
+
+import android.content.Context
+import android.os.Bundle
+import android.view.View
+import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat
+import androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat
+import kotlin.Int
+
+public abstract class AbstractSandboxedSdkProviderCompat : SandboxedSdkProviderCompat() {
+  public override fun onLoadSdk(params: Bundle): SandboxedSdkCompat {
+    val sdk = createMySdk(context!!)
+    return SandboxedSdkCompat(MySdkStubDelegate(sdk))
+  }
+
+  public override fun getView(
+    windowContext: Context,
+    params: Bundle,
+    width: Int,
+    height: Int,
+  ): View {
+    TODO("Implement")
+  }
+
+  protected abstract fun createMySdk(context: Context): MySdk
+}
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkruntimelibrarysdk/input/com/mysdk/BackwardsCompatibleSdk.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/input/com/mysdk/WithoutRuntimeLibrarySdk.kt
similarity index 89%
rename from privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkruntimelibrarysdk/input/com/mysdk/BackwardsCompatibleSdk.kt
rename to privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/input/com/mysdk/WithoutRuntimeLibrarySdk.kt
index 8424489..c06a208 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkruntimelibrarysdk/input/com/mysdk/BackwardsCompatibleSdk.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/input/com/mysdk/WithoutRuntimeLibrarySdk.kt
@@ -6,6 +6,6 @@
 import androidx.privacysandbox.tools.PrivacySandboxValue
 
 @PrivacySandboxService
-interface BackwardsCompatibleSdk {
+interface WithoutRuntimeLibrarySdk {
     suspend fun doStuff(x: Int, y: Int): String
 }
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/AbstractSandboxedSdkProviderCompat.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/AbstractSandboxedSdkProviderCompat.kt
new file mode 100644
index 0000000..744be58
--- /dev/null
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/AbstractSandboxedSdkProviderCompat.kt
@@ -0,0 +1,26 @@
+package com.mysdk
+
+import android.app.sdksandbox.SandboxedSdk
+import android.app.sdksandbox.SandboxedSdkProvider
+import android.content.Context
+import android.os.Bundle
+import android.view.View
+import kotlin.Int
+
+public abstract class AbstractSandboxedSdkProviderCompat : SandboxedSdkProvider() {
+  public override fun onLoadSdk(params: Bundle): SandboxedSdk {
+    val sdk = createWithoutRuntimeLibrarySdk(context!!)
+    return SandboxedSdk(WithoutRuntimeLibrarySdkStubDelegate(sdk))
+  }
+
+  public override fun getView(
+    windowContext: Context,
+    params: Bundle,
+    width: Int,
+    height: Int,
+  ): View {
+    TODO("Implement")
+  }
+
+  protected abstract fun createWithoutRuntimeLibrarySdk(context: Context): WithoutRuntimeLibrarySdk
+}
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkruntimelibrarysdk/output/com/mysdk/PrivacySandboxThrowableParcelConverter.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/PrivacySandboxThrowableParcelConverter.kt
similarity index 100%
rename from privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkruntimelibrarysdk/output/com/mysdk/PrivacySandboxThrowableParcelConverter.kt
rename to privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/PrivacySandboxThrowableParcelConverter.kt
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkruntimelibrarysdk/output/com/mysdk/TransportCancellationCallback.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/TransportCancellationCallback.kt
similarity index 100%
rename from privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkruntimelibrarysdk/output/com/mysdk/TransportCancellationCallback.kt
rename to privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/TransportCancellationCallback.kt
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/WithoutRuntimeLibrarySdkFactory.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/WithoutRuntimeLibrarySdkFactory.kt
new file mode 100644
index 0000000..71eaa79
--- /dev/null
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/WithoutRuntimeLibrarySdkFactory.kt
@@ -0,0 +1,11 @@
+package com.mysdk
+
+import android.os.IBinder
+import androidx.privacysandbox.tools.`internal`.GeneratedPublicApi
+
+@GeneratedPublicApi
+public object WithoutRuntimeLibrarySdkFactory {
+    @Suppress("UNUSED_PARAMETER")
+    public fun wrapToWithoutRuntimeLibrarySdk(binder: IBinder): WithoutRuntimeLibrarySdk = throw
+            RuntimeException("Stub!")
+}
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkruntimelibrarysdk/output/com/mysdk/BackwardsCompatibleSdkStubDelegate.kt b/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/WithoutRuntimeLibrarySdkStubDelegate.kt
similarity index 83%
rename from privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkruntimelibrarysdk/output/com/mysdk/BackwardsCompatibleSdkStubDelegate.kt
rename to privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/WithoutRuntimeLibrarySdkStubDelegate.kt
index bca930d..3a82d43 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/test-data/sdkruntimelibrarysdk/output/com/mysdk/BackwardsCompatibleSdkStubDelegate.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/test-data/withoutruntimelibrarysdk/output/com/mysdk/WithoutRuntimeLibrarySdkStubDelegate.kt
@@ -8,9 +8,9 @@
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.launch
 
-public class BackwardsCompatibleSdkStubDelegate internal constructor(
-  public val `delegate`: BackwardsCompatibleSdk,
-) : IBackwardsCompatibleSdk.Stub() {
+public class WithoutRuntimeLibrarySdkStubDelegate internal constructor(
+  public val `delegate`: WithoutRuntimeLibrarySdk,
+) : IWithoutRuntimeLibrarySdk.Stub() {
   public override fun doStuff(
     x: Int,
     y: Int,
diff --git a/room/integration-tests/incremental-annotation-processing/src/test/kotlin/androidx/room/gradle/RoomIncrementalAnnotationProcessingTest.kt b/room/integration-tests/incremental-annotation-processing/src/test/kotlin/androidx/room/gradle/RoomIncrementalAnnotationProcessingTest.kt
index d6096c6..8720b6e 100644
--- a/room/integration-tests/incremental-annotation-processing/src/test/kotlin/androidx/room/gradle/RoomIncrementalAnnotationProcessingTest.kt
+++ b/room/integration-tests/incremental-annotation-processing/src/test/kotlin/androidx/room/gradle/RoomIncrementalAnnotationProcessingTest.kt
@@ -212,6 +212,14 @@
                 $processorConfiguration "androidx.room:room-compiler:$roomVersion"
             }
 
+            tasks.withType(
+                org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+            ).configureEach {
+                kotlinOptions {
+                    jvmTarget = "1.8"
+                }
+            }
+
             class SchemaLocationArgumentProvider implements CommandLineArgumentProvider {
 
                 @OutputDirectory
@@ -237,6 +245,11 @@
                         }
                     }
                 }
+
+                compileOptions {
+                  sourceCompatibility = JavaVersion.VERSION_1_8
+                  targetCompatibility = JavaVersion.VERSION_1_8
+                }
             }
             $kspArgumentsBlock
         """
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt
index d5a9334..675a185 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt
@@ -18,6 +18,8 @@
 
 import android.content.Context
 import android.os.Build
+import android.os.StrictMode
+import android.os.StrictMode.ThreadPolicy
 import androidx.arch.core.executor.ArchTaskExecutor
 import androidx.arch.core.executor.TaskExecutor
 import androidx.room.Room
@@ -33,6 +35,7 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import java.io.IOException
@@ -153,6 +156,31 @@
     }
 
     @Test
+    fun allBookSuspend_autoClose() {
+        val context: Context = ApplicationProvider.getApplicationContext()
+        context.deleteDatabase("autoClose.db")
+        val db = Room.databaseBuilder(
+            context = context,
+            klass = TestDatabase::class.java,
+            name = "test.db"
+        ).setAutoCloseTimeout(10, TimeUnit.MILLISECONDS).build()
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            StrictMode.setThreadPolicy(
+                ThreadPolicy.Builder()
+                    .detectDiskReads()
+                    .detectDiskWrites()
+                    .penaltyDeath()
+                    .build()
+            )
+            runBlocking {
+                db.booksDao().getBooksSuspend()
+                delay(100) // let db auto-close
+                db.booksDao().getBooksSuspend()
+            }
+        }
+    }
+
+    @Test
     @Suppress("DEPRECATION")
     fun suspendingBlock_beginEndTransaction() {
         runBlocking {
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/DelegatingTestRegistrar.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/DelegatingTestRegistrar.kt
index ae10928..2cc1938 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/DelegatingTestRegistrar.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/DelegatingTestRegistrar.kt
@@ -17,24 +17,27 @@
 package androidx.room.compiler.processing.util.compiler
 
 import androidx.room.compiler.processing.util.compiler.DelegatingTestRegistrar.Companion.runCompilation
+import java.net.URI
+import java.nio.file.Paths
 import org.jetbrains.kotlin.cli.common.ExitCode
 import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
 import org.jetbrains.kotlin.cli.common.messages.MessageCollector
 import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
-import org.jetbrains.kotlin.cli.jvm.plugins.ServiceLoaderLite
 import org.jetbrains.kotlin.com.intellij.mock.MockProject
-import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
+import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
 import org.jetbrains.kotlin.config.CompilerConfiguration
 import org.jetbrains.kotlin.config.Services
-import java.net.URI
-import java.nio.file.Paths
+import org.jetbrains.kotlin.util.ServiceLoaderLite
 
 /**
  * A component registrar for Kotlin Compiler that delegates to a list of thread local delegates.
  *
  * see [runCompilation] for usages.
  */
-internal class DelegatingTestRegistrar : ComponentRegistrar {
+@Suppress("DEPRECATION") // TODO: Migrate ComponentRegistrar to CompilerPluginRegistrar
+@OptIn(ExperimentalCompilerApi::class)
+internal class DelegatingTestRegistrar :
+    @Suppress("DEPRECATION") org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar {
     override fun registerProjectComponents(
         project: MockProject,
         configuration: CompilerConfiguration
@@ -63,7 +66,8 @@
                 }
                 .find { resourcesPath ->
                     ServiceLoaderLite.findImplementations(
-                        ComponentRegistrar::class.java,
+                        @Suppress("DEPRECATION")
+                        org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar::class.java,
                         listOf(resourcesPath.toFile())
                     ).any { implementation ->
                         implementation == DelegatingTestRegistrar::class.java.name
@@ -76,12 +80,15 @@
                     """.trimIndent()
                 )
         }
-        private val delegates = ThreadLocal<List<ComponentRegistrar>>()
+        @Suppress("DEPRECATION")
+        private val delegates =
+            ThreadLocal<List<org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar>>()
         fun runCompilation(
             compiler: K2JVMCompiler,
             messageCollector: MessageCollector,
             arguments: K2JVMCompilerArguments,
-            pluginRegistrars: List<ComponentRegistrar>
+            @Suppress("DEPRECATION")
+            pluginRegistrars: List<org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar>
         ): ExitCode {
             try {
                 arguments.addDelegatingTestRegistrar()
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/KotlinCliRunner.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/KotlinCliRunner.kt
index 9b1f573..5b591f0 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/KotlinCliRunner.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/KotlinCliRunner.kt
@@ -24,7 +24,7 @@
 import org.jetbrains.kotlin.cli.common.ExitCode
 import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
 import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
-import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
+import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
 import org.jetbrains.kotlin.config.JvmDefaultMode
 import org.jetbrains.kotlin.config.JvmTarget
 
@@ -77,6 +77,7 @@
     /**
      * Runs the kotlin cli API with the given arguments.
      */
+    @OptIn(ExperimentalCompilerApi::class)
     fun runKotlinCli(
         /**
          * Compilation arguments (sources, classpaths etc)
@@ -89,7 +90,8 @@
         /**
          * List of component registrars for the compilation.
          */
-        pluginRegistrars: List<ComponentRegistrar>
+        @Suppress("DEPRECATION")
+        pluginRegistrars: List<org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar>
     ): KotlinCliResult {
         val cliArguments = compiler.createArguments()
         destinationDir.mkdirs()
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKapt3Registrar.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKapt3Registrar.kt
index f38d3ad..8dca55a 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKapt3Registrar.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKapt3Registrar.kt
@@ -17,6 +17,8 @@
 
 package androidx.room.compiler.processing.util.compiler
 
+import java.io.File
+import javax.annotation.processing.Processor
 import org.jetbrains.kotlin.base.kapt3.KaptFlag
 import org.jetbrains.kotlin.base.kapt3.KaptOptions
 import org.jetbrains.kotlin.base.kapt3.logString
@@ -25,7 +27,7 @@
 import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
 import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
 import org.jetbrains.kotlin.com.intellij.mock.MockProject
-import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
+import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
 import org.jetbrains.kotlin.config.CompilerConfiguration
 import org.jetbrains.kotlin.config.JVMConfigurationKeys
 import org.jetbrains.kotlin.container.StorageComponentContainer
@@ -38,13 +40,11 @@
 import org.jetbrains.kotlin.kapt3.base.incremental.DeclaredProcType
 import org.jetbrains.kotlin.kapt3.base.incremental.IncrementalProcessor
 import org.jetbrains.kotlin.kapt3.util.MessageCollectorBackedKaptLogger
+import org.jetbrains.kotlin.kapt3.util.doOpenInternalPackagesIfRequired
 import org.jetbrains.kotlin.platform.TargetPlatform
 import org.jetbrains.kotlin.platform.jvm.isJvm
 import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
 import org.jetbrains.kotlin.resolve.jvm.extensions.PartialAnalysisHandlerExtension
-import java.io.File
-import javax.annotation.processing.Processor
-import org.jetbrains.kotlin.kapt3.util.doOpenInternalPackagesIfRequired
 
 /**
  * Registers the KAPT component for the kotlin compilation.
@@ -53,11 +53,13 @@
  * https://github.com/JetBrains/kotlin/blob/master/plugins/kapt3/kapt3-compiler/src/
  *  org/jetbrains/kotlin/kapt3/Kapt3Plugin.kt
  */
+@Suppress("DEPRECATION") // TODO: Migrate ComponentRegistrar to CompilerPluginRegistrar
+@OptIn(ExperimentalCompilerApi::class)
 internal class TestKapt3Registrar(
     val processors: List<Processor>,
     val baseOptions: KaptOptions.Builder,
     val messageCollector: MessageCollector
-) : ComponentRegistrar {
+) : @Suppress("DEPRECATION") org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar {
     override fun registerProjectComponents(
         project: MockProject,
         configuration: CompilerConfiguration
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKspRegistrar.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKspRegistrar.kt
index b4104d3..9ab12a4 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKspRegistrar.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/TestKspRegistrar.kt
@@ -22,6 +22,7 @@
 import com.google.devtools.ksp.processing.KSPLogger
 import com.google.devtools.ksp.processing.SymbolProcessorProvider
 import com.google.devtools.ksp.processing.impl.MessageCollectorBasedKSPLogger
+import java.io.File
 import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
 import org.jetbrains.kotlin.cli.common.messages.MessageCollector
 import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
@@ -30,21 +31,22 @@
 import org.jetbrains.kotlin.com.intellij.mock.MockProject
 import org.jetbrains.kotlin.com.intellij.psi.PsiTreeChangeAdapter
 import org.jetbrains.kotlin.com.intellij.psi.PsiTreeChangeListener
-import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
+import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
 import org.jetbrains.kotlin.config.CompilerConfiguration
 import org.jetbrains.kotlin.resolve.extensions.AnalysisHandlerExtension
-import java.io.File
 
 /**
  * Registers the KSP component for the kotlin compilation.
  */
+@Suppress("DEPRECATION") // TODO: Migrate ComponentRegistrar to CompilerPluginRegistrar
+@OptIn(ExperimentalCompilerApi::class)
 internal class TestKspRegistrar(
     val kspWorkingDir: File,
     val baseOptions: KspOptions.Builder,
 
     val processorProviders: List<SymbolProcessorProvider>,
     val messageCollector: MessageCollector
-) : ComponentRegistrar {
+) : @Suppress("DEPRECATION") org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar {
     override fun registerProjectComponents(
         project: MockProject,
         configuration: CompilerConfiguration
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KaptCompilationStep.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KaptCompilationStep.kt
index e968381..2b8bc91 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KaptCompilationStep.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KaptCompilationStep.kt
@@ -26,7 +26,8 @@
 import org.jetbrains.kotlin.base.kapt3.KaptFlag
 import org.jetbrains.kotlin.base.kapt3.KaptOptions
 import org.jetbrains.kotlin.cli.common.ExitCode
-import org.jetbrains.kotlin.compiler.plugin.parsePluginOption
+import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
+import org.jetbrains.kotlin.compiler.plugin.parseLegacyPluginOption
 
 /**
  * Runs KAPT to run annotation processors.
@@ -72,6 +73,7 @@
         }
     }
 
+    @OptIn(ExperimentalCompilerApi::class)
     override fun execute(
         workingDir: File,
         arguments: CompilationStepArguments
@@ -131,7 +133,7 @@
             val options = kotlincArguments.dropLast(1).zip(kotlincArguments.drop(1))
                 .filter { it.first == "-P" }
                 .mapNotNull {
-                    parsePluginOption(it.second)
+                    parseLegacyPluginOption(it.second)
                 }
             val filteredOptionsMap = options
                 .filter { it.pluginId == pluginId }
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KotlinSourceCompilationStep.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KotlinSourceCompilationStep.kt
index dcfe22b..aa80eaf 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KotlinSourceCompilationStep.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KotlinSourceCompilationStep.kt
@@ -17,6 +17,7 @@
 package androidx.room.compiler.processing.util.compiler.steps
 
 import androidx.room.compiler.processing.util.compiler.KotlinCliRunner
+import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
 import org.jetbrains.kotlin.cli.common.ExitCode
 import java.io.File
 
@@ -28,6 +29,7 @@
 internal object KotlinSourceCompilationStep : KotlinCompilationStep {
     override val name = "kotlinSourceCompilation"
 
+    @OptIn(ExperimentalCompilerApi::class)
     override fun execute(
         workingDir: File,
         arguments: CompilationStepArguments
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KspCompilationStep.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KspCompilationStep.kt
index 392de38..5a9069a 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KspCompilationStep.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/compiler/steps/KspCompilationStep.kt
@@ -24,6 +24,7 @@
 import com.google.devtools.ksp.processing.SymbolProcessorProvider
 import java.io.File
 import org.jetbrains.kotlin.cli.common.ExitCode
+import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
 
 /**
  * Runs the Symbol Processors
@@ -44,6 +45,7 @@
         }
     }
 
+    @OptIn(ExperimentalCompilerApi::class)
     override fun execute(
         workingDir: File,
         arguments: CompilationStepArguments
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
index 7e7dc7b..df2a0b3 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/codegen/XTypeName.kt
@@ -279,6 +279,7 @@
     val packageName: String = java.packageName()
     val simpleNames: List<String> = java.simpleNames()
     val canonicalName: String = java.canonicalName()
+    val reflectionName: String = java.reflectionName()
 
     /**
      * Returns a parameterized type, applying the `typeArguments` to `this`.
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
index 9256458..b1a65c0 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
@@ -158,11 +158,16 @@
      * already [XNullability.NONNULL].
      */
     fun makeNonNullable(): XType
+}
 
-    /**
-     * Returns true if this type is a type variable.
-     */
-    fun isTypeVariable(): Boolean
+/**
+ * Returns true if this type is a [XTypeVariableType].
+ */
+fun XType.isTypeVariable(): Boolean {
+    contract {
+        returns(true) implies (this@isTypeVariable is XTypeVariableType)
+    }
+    return this is XTypeVariableType
 }
 
 /**
@@ -209,16 +214,16 @@
 }
 
 /**
- * Returns `true` if this is a primitive or boxed it
+ * Returns `true` if this is a primitive or boxed int
  */
 fun XType.isInt(): Boolean = asTypeName() == XTypeName.PRIMITIVE_INT ||
-    asTypeName().equalsIgnoreNullability(KnownTypeNames.BOXED_INT)
+    asTypeName().equalsIgnoreNullability(XTypeName.BOXED_INT)
 
 /**
  * Returns `true` if this is a primitive or boxed long
  */
 fun XType.isLong(): Boolean = asTypeName() == XTypeName.PRIMITIVE_LONG ||
-    asTypeName().equalsIgnoreNullability(KnownTypeNames.BOXED_LONG)
+    asTypeName().equalsIgnoreNullability(XTypeName.BOXED_LONG)
 
 /**
  * Returns `true` if this is `void`
@@ -239,12 +244,39 @@
  * Returns `true` if this represents a `byte`.
  */
 fun XType.isByte(): Boolean = asTypeName() == XTypeName.PRIMITIVE_BYTE ||
-    asTypeName().equalsIgnoreNullability(KnownTypeNames.BOXED_BYTE)
+    asTypeName().equalsIgnoreNullability(XTypeName.BOXED_BYTE)
+
+/**
+ * Returns `true` if this represents a `short`.
+ */
+fun XType.isShort(): Boolean = asTypeName() == XTypeName.PRIMITIVE_SHORT ||
+    asTypeName().equalsIgnoreNullability(XTypeName.BOXED_SHORT)
+
+/**
+ * Returns `true` if this represents a `float`.
+ */
+fun XType.isFloat(): Boolean = asTypeName() == XTypeName.PRIMITIVE_FLOAT ||
+    asTypeName().equalsIgnoreNullability(XTypeName.BOXED_FLOAT)
+
+/**
+ * Returns `true` if this represents a `double`.
+ */
+fun XType.isDouble(): Boolean = asTypeName() == XTypeName.PRIMITIVE_DOUBLE ||
+    asTypeName().equalsIgnoreNullability(XTypeName.BOXED_DOUBLE)
+
+/**
+ * Returns `true` if this represents a `boolean`.
+ */
+fun XType.isBoolean(): Boolean = asTypeName() == XTypeName.PRIMITIVE_BOOLEAN ||
+    asTypeName().equalsIgnoreNullability(XTypeName.BOXED_BOOLEAN)
+
+/**
+ * Returns `true` if this represents a `char`.
+ */
+fun XType.isChar(): Boolean = asTypeName() == XTypeName.PRIMITIVE_CHAR ||
+    asTypeName().equalsIgnoreNullability(XTypeName.BOXED_CHAR)
 
 internal object KnownTypeNames {
     val BOXED_VOID = Void::class.asClassName()
-    val BOXED_INT = Int::class.asClassName()
-    val BOXED_LONG = Long::class.asClassName()
-    val BOXED_BYTE = Byte::class.asClassName()
     val KOTLIN_UNIT = XClassName.get("kotlin", "Unit")
 }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeVariableType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeVariableType.kt
new file mode 100644
index 0000000..d061e3f
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XTypeVariableType.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.room.compiler.processing
+
+/**
+ * Represents a type variable.
+ *
+ * @see [javax.lang.model.type.TypeVariable]
+ * @see [com.google.devtools.ksp.symbol.KSTypeParameter]
+ */
+interface XTypeVariableType : XType {
+    /**
+     * The upper bounds of the type variable.
+     *
+     * Note that this model differs a bit from the Javac model where
+     * [javax.lang.model.type.TypeVariable] always has a single `upperBound` which may end up
+     * resolving to an [javax.lang.model.type.IntersectionType]. Instead, this model is closer to
+     * the KSP model where the type variable may return multiple upper bounds when an intersection
+     * type exists rather than representing the intersection type as a unique type in the model.
+     */
+    val upperBounds: List<XType>
+}
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacMethodElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacMethodElement.kt
index 4af85f3..745a16d 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacMethodElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacMethodElement.kt
@@ -51,8 +51,8 @@
 
     override val typeParameters: List<XTypeParameterElement> by lazy {
         element.typeParameters.mapIndexed { index, typeParameter ->
-            val typeArgument = kotlinMetadata?.typeArguments?.get(index)
-            JavacTypeParameterElement(env, this, typeParameter, typeArgument)
+            val typeParameterMetadata = kotlinMetadata?.typeParameters?.get(index)
+            JavacTypeParameterElement(env, this, typeParameter, typeParameterMetadata)
         }
     }
 
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt
index c9c5393..2140436 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt
@@ -225,6 +225,29 @@
                         )
                     }
                 }
+            TypeKind.TYPEVAR ->
+                when {
+                    kotlinType != null -> {
+                        JavacTypeVariableType(
+                            env = this,
+                            typeMirror = MoreTypes.asTypeVariable(typeMirror),
+                            kotlinType = kotlinType
+                        )
+                    }
+                    elementNullability != null -> {
+                        JavacTypeVariableType(
+                            env = this,
+                            typeMirror = MoreTypes.asTypeVariable(typeMirror),
+                            nullability = elementNullability
+                        )
+                    }
+                    else -> {
+                        JavacTypeVariableType(
+                            env = this,
+                            typeMirror = MoreTypes.asTypeVariable(typeMirror)
+                        )
+                    }
+                }
             else ->
                 when {
                     kotlinType != null -> {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
index e5463e1..7457b78 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
@@ -200,6 +200,4 @@
                 "TypeMirror#toXProcessing(XProcessingEnv)?"
         )
     }
-
-    override fun isTypeVariable() = typeMirror.kind == TypeKind.TYPEVAR
 }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
index dc868ae..505e75f 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeElement.kt
@@ -86,8 +86,8 @@
 
     override val typeParameters: List<XTypeParameterElement> by lazy {
         element.typeParameters.mapIndexed { index, typeParameter ->
-            val typeArgument = kotlinMetadata?.kmType?.typeArguments?.get(index)
-            JavacTypeParameterElement(env, this, typeParameter, typeArgument)
+            val typeParameterMetadata = kotlinMetadata?.typeParameters?.get(index)
+            JavacTypeParameterElement(env, this, typeParameter, typeParameterMetadata)
         }
     }
 
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeParameterElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeParameterElement.kt
index 5f15f95..50652e4 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeParameterElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeParameterElement.kt
@@ -21,7 +21,7 @@
 import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XTypeParameterElement
-import androidx.room.compiler.processing.javac.kotlin.KmType
+import androidx.room.compiler.processing.javac.kotlin.KmTypeParameter
 import com.squareup.javapoet.TypeVariableName
 import javax.lang.model.element.TypeParameterElement
 
@@ -29,9 +29,8 @@
     env: JavacProcessingEnv,
     override val enclosingElement: XElement,
     override val element: TypeParameterElement,
-    private val kotlinType: KmType?,
+    override val kotlinMetadata: KmTypeParameter?,
 ) : JavacElement(env, element), XTypeParameterElement {
-    override val kotlinMetadata = null
 
     override val name: String
         get() = element.simpleName.toString()
@@ -41,7 +40,9 @@
     }
 
     override val bounds: List<XType> by lazy {
-        element.bounds.map { env.wrap(it, kotlinType?.extendsBound, XNullability.UNKNOWN) }
+        element.bounds.mapIndexed { i, bound ->
+            env.wrap(bound, kotlinMetadata?.upperBounds?.getOrNull(i), XNullability.UNKNOWN)
+        }
     }
 
     override val fallbackLocationText: String
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeVariableType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeVariableType.kt
new file mode 100644
index 0000000..d66860f
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacTypeVariableType.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.room.compiler.processing.javac
+
+import androidx.room.compiler.processing.XNullability
+import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.XTypeVariableType
+import androidx.room.compiler.processing.javac.kotlin.KmType
+import com.google.auto.common.MoreTypes.asIntersection
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeVariable
+
+internal class JavacTypeVariableType(
+    env: JavacProcessingEnv,
+    override val typeMirror: TypeVariable,
+    nullability: XNullability?,
+    override val kotlinType: KmType?
+) : JavacType(env, typeMirror, nullability), XTypeVariableType {
+    constructor(
+        env: JavacProcessingEnv,
+        typeMirror: TypeVariable
+    ) : this(
+        env = env,
+        typeMirror = typeMirror,
+        nullability = null,
+        kotlinType = null
+    )
+
+    constructor(
+        env: JavacProcessingEnv,
+        typeMirror: TypeVariable,
+        kotlinType: KmType
+    ) : this(
+        env = env,
+        typeMirror = typeMirror,
+        nullability = kotlinType.nullability,
+        kotlinType = kotlinType
+    )
+
+    constructor(
+        env: JavacProcessingEnv,
+        typeMirror: TypeVariable,
+        nullability: XNullability
+    ) : this(
+        env = env,
+        typeMirror = typeMirror,
+        nullability = nullability,
+        kotlinType = null
+    )
+
+    override val equalityItems by lazy {
+        arrayOf(typeMirror)
+    }
+
+    override val typeArguments: List<XType>
+        get() = emptyList()
+
+    override val upperBounds: List<XType>
+        get() {
+            return if (typeMirror.upperBound.kind == TypeKind.INTERSECTION) {
+                asIntersection(typeMirror.upperBound).bounds.mapIndexed { i, bound ->
+                    env.wrap(
+                        typeMirror = bound,
+                        kotlinType = kotlinType?.upperBounds?.getOrNull(i),
+                        elementNullability = maybeNullability
+                    )
+                }
+            } else {
+                listOf(
+                    env.wrap(
+                        typeMirror = typeMirror.upperBound,
+                        // If this isn't an intersection type then there is only 1 upper bound
+                        kotlinType = kotlinType?.upperBounds?.singleOrNull(),
+                        elementNullability = maybeNullability
+                    )
+                )
+            }
+        }
+
+    override fun copyWithNullability(nullability: XNullability): JavacTypeVariableType {
+        return JavacTypeVariableType(
+            env = env,
+            typeMirror = typeMirror,
+            kotlinType = kotlinType,
+            nullability = nullability
+        )
+    }
+}
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/KmTypeExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/KmTypeExt.kt
index 8725cbe..a81248e 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/KmTypeExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/KmTypeExt.kt
@@ -24,5 +24,9 @@
         XNullability.NULLABLE
     } else {
         // if there is an upper bound information, use its nullability (e.g. it might be T : Foo?)
-        extendsBound?.nullability ?: XNullability.NONNULL
+        if (upperBounds?.all { it.nullability == XNullability.NULLABLE } == true) {
+            XNullability.NULLABLE
+        } else {
+            extendsBound?.nullability ?: XNullability.NONNULL
+        }
     }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/JvmDescriptorUtils.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/JvmDescriptorUtils.kt
index dbfc124..4453eb7 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/JvmDescriptorUtils.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/JvmDescriptorUtils.kt
@@ -16,7 +16,6 @@
 
 package androidx.room.compiler.processing.javac.kotlin
 
-import com.google.auto.common.MoreTypes
 import com.squareup.javapoet.ArrayTypeName
 import com.squareup.javapoet.ClassName
 import com.squareup.javapoet.TypeName
@@ -53,51 +52,9 @@
  *
  * For reference, see the [JVM specification, section 4.3.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.3)
  */
-internal fun ExecutableElement.descriptor() =
-    "$simpleName${MoreTypes.asExecutable(asType()).descriptor()}"
+internal fun ExecutableElement.descriptor() = "$simpleName${asType().descriptor()}"
 
-/**
- * Returns the name of this [TypeElement] in its "internal form".
- *
- * For reference, see the [JVM specification, section 4.2](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.2).
- */
-internal val Element.internalName: String
-    get() = when (this) {
-        is TypeElement ->
-            when (nestingKind) {
-                NestingKind.TOP_LEVEL ->
-                    qualifiedName.toString().replace('.', '/')
-                NestingKind.MEMBER, NestingKind.LOCAL ->
-                    enclosingElement.internalName + "$" + simpleName
-                NestingKind.ANONYMOUS ->
-                    error("Unsupported nesting $nestingKind")
-                else ->
-                    error("Unsupported, nestingKind == null")
-            }
-        is ExecutableElement -> enclosingElement.internalName
-        is QualifiedNameable -> qualifiedName.toString().replace('.', '/')
-        else -> simpleName.toString()
-    }
-
-@Suppress("unused")
-internal val NoType.descriptor: String
-    get() = "V"
-
-internal val DeclaredType.descriptor: String
-    get() = "L" + asElement().internalName + ";"
-
-internal val PrimitiveType.descriptor: String
-    get() = when (this.kind) {
-        TypeKind.BYTE -> "B"
-        TypeKind.CHAR -> "C"
-        TypeKind.DOUBLE -> "D"
-        TypeKind.FLOAT -> "F"
-        TypeKind.INT -> "I"
-        TypeKind.LONG -> "J"
-        TypeKind.SHORT -> "S"
-        TypeKind.BOOLEAN -> "Z"
-        else -> error("Unknown primitive type $this")
-    }
+private fun TypeMirror.descriptor() = JvmDescriptorTypeVisitor.visit(this)
 
 // see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.2-200
 internal fun String.typeNameFromJvmSignature(): TypeName {
@@ -146,61 +103,78 @@
     }
 }
 
-internal fun TypeMirror.descriptor(): String = accept(JvmDescriptorTypeVisitor, Unit)
-
-@Suppress("unused")
-internal fun WildcardType.descriptor(): String = ""
-
-// The erasure of a type variable is the erasure of its leftmost bound. - JVM Spec Sec 4.6
-internal fun TypeVariable.descriptor(): String = this.upperBound.descriptor()
-
-// For a type variable with multiple bounds: "the erasure of a type variable is determined by
-// the first type in its bound" - JVM Spec Sec 4.4
-internal fun IntersectionType.descriptor(): String =
-    this.bounds[0].descriptor()
-
-internal fun ArrayType.descriptor(): String =
-    "[" + componentType.descriptor()
-
-internal fun ExecutableType.descriptor(): String {
-    val parameterDescriptors =
-        parameterTypes.joinToString(separator = "") { it.descriptor() }
-    val returnDescriptor = returnType.descriptor()
-    return "($parameterDescriptors)$returnDescriptor"
-}
-
 /**
  * When applied over a type, it returns either:
  * + a "field descriptor", for example: `Ljava/lang/Object;`
  * + a "method descriptor", for example: `(Ljava/lang/Object;)Z`
  *
- * The easiest way to use this is through [TypeMirror.descriptor]
- *
  * For reference, see the [JVM specification, section 4.3](http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
  */
-internal object JvmDescriptorTypeVisitor : AbstractTypeVisitor8<String, Unit>() {
+private object JvmDescriptorTypeVisitor : AbstractTypeVisitor8<String, Any?>() {
 
-    override fun visitNoType(t: NoType, u: Unit): String = t.descriptor
+    override fun visitNoType(t: NoType, u: Any?): String = "V"
 
-    override fun visitDeclared(t: DeclaredType, u: Unit): String = t.descriptor
+    override fun visitDeclared(t: DeclaredType, u: Any?): String = "L${t.asElement().internalName};"
 
-    override fun visitPrimitive(t: PrimitiveType, u: Unit): String = t.descriptor
+    override fun visitPrimitive(t: PrimitiveType, u: Any?): String {
+        return when (t.kind) {
+            TypeKind.BYTE -> "B"
+            TypeKind.CHAR -> "C"
+            TypeKind.DOUBLE -> "D"
+            TypeKind.FLOAT -> "F"
+            TypeKind.INT -> "I"
+            TypeKind.LONG -> "J"
+            TypeKind.SHORT -> "S"
+            TypeKind.BOOLEAN -> "Z"
+            else -> error("Unknown primitive type $this")
+        }
+    }
 
-    override fun visitArray(t: ArrayType, u: Unit): String = t.descriptor()
+    override fun visitArray(t: ArrayType, u: Any?): String = "[" + visit(t.componentType)
 
-    override fun visitWildcard(t: WildcardType, u: Unit): String = t.descriptor()
+    override fun visitWildcard(t: WildcardType, u: Any?): String = visitUnknown(t, u)
 
-    override fun visitExecutable(t: ExecutableType, u: Unit): String = t.descriptor()
+    override fun visitExecutable(t: ExecutableType, u: Any?): String {
+        val parameterDescriptors = t.parameterTypes.joinToString("") { visit(it) }
+        val returnDescriptor = visit(t.returnType)
+        return "($parameterDescriptors)$returnDescriptor"
+    }
 
-    override fun visitTypeVariable(t: TypeVariable, u: Unit): String = t.descriptor()
+    override fun visitTypeVariable(t: TypeVariable, u: Any?): String = visit(t.upperBound)
 
-    override fun visitNull(t: NullType, u: Unit): String = visitUnknown(t, u)
+    override fun visitNull(t: NullType, u: Any?): String = visitUnknown(t, u)
 
-    override fun visitError(t: ErrorType, u: Unit): String = visitDeclared(t, u)
+    override fun visitError(t: ErrorType, u: Any?): String = visitDeclared(t, u)
 
-    override fun visitIntersection(t: IntersectionType, u: Unit) = t.descriptor()
+    // For a type variable with multiple bounds: "the erasure of a type variable is determined
+    // by the first type in its bound" - JLS Sec 4.4
+    // See https://docs.oracle.com/javase/specs/jls/se16/html/jls-4.html#jls-4.4
+    override fun visitIntersection(t: IntersectionType, u: Any?): String = visit(t.bounds[0])
 
-    override fun visitUnion(t: UnionType, u: Unit) = visitUnknown(t, u)
+    override fun visitUnion(t: UnionType, u: Any?): String = visitUnknown(t, u)
 
-    override fun visitUnknown(t: TypeMirror, u: Unit): String = error("Unsupported type $t")
+    override fun visitUnknown(t: TypeMirror, u: Any?): String = error("Unsupported type $t")
+
+    /**
+     * Returns the name of this [TypeElement] in its "internal form".
+     *
+     * For reference, see the [JVM specification, section 4.2](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.2).
+     */
+    private val Element.internalName: String
+        get() = when (this) {
+            is TypeElement ->
+                when (nestingKind) {
+                    NestingKind.TOP_LEVEL ->
+                        qualifiedName.toString().replace('.', '/')
+                    NestingKind.MEMBER, NestingKind.LOCAL ->
+                        enclosingElement.internalName + "$" + simpleName
+                    NestingKind.ANONYMOUS ->
+                        error("Unsupported nesting $nestingKind")
+                    else ->
+                        error("Unsupported, nestingKind == null")
+                }
+            is ExecutableElement -> enclosingElement.internalName
+            is QualifiedNameable -> qualifiedName.toString().replace('.', '/')
+            else -> simpleName.toString()
+        }
 }
\ No newline at end of file
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 f6e76c6..e0ccee2 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
@@ -66,7 +66,7 @@
     val name: String,
     val descriptor: String,
     override val flags: Flags,
-    val typeArguments: List<KmType>,
+    val typeParameters: List<KmTypeParameter>,
     override val parameters: List<KmValueParameter>,
     val returnType: KmType,
     val receiverType: KmType?,
@@ -103,22 +103,32 @@
 internal data class KmType(
     override val flags: Flags,
     val typeArguments: List<KmType>,
-    val extendsBound: KmType?,
+    /** The extends bounds are only non-null for wildcard (i.e. in/out variant) types. */
+    val extendsBound: KmType? = null,
+    /** The upper bounds are only non-null for type variable types with upper bounds. */
+    val upperBounds: List<KmType>? = null,
     val isExtensionType: Boolean
 ) : KmElement {
     fun isNullable() = Flag.Type.IS_NULLABLE(flags)
-    fun erasure(): KmType = KmType(flags, emptyList(), extendsBound?.erasure(), isExtensionType)
+    fun erasure(): KmType = KmType(
+        flags,
+        emptyList(),
+        extendsBound?.erasure(),
+        // The erasure of a type variable is equal to the erasure of the first upper bound.
+        upperBounds?.firstOrNull()?.erasure()?.let { listOf(it) },
+        isExtensionType
+    )
 }
 
-private data class KmTypeParameter(
+internal data class KmTypeParameter(
     val name: String,
     override val flags: Flags,
-    val extendsBound: KmType?
+    val upperBounds: List<KmType>
 ) : KmElement {
     fun asKmType() = KmType(
         flags = flags,
         typeArguments = emptyList(),
-        extendsBound = extendsBound,
+        upperBounds = upperBounds,
         isExtensionType = false
     )
 }
@@ -137,7 +147,8 @@
 
 internal data class KmClassTypeInfo(
     val kmType: KmType,
-    val superType: KmType?
+    val superType: KmType?,
+    val typeParameters: List<KmTypeParameter>
 )
 
 internal fun KotlinClassMetadata.Class.readFunctions(): List<KmFunction> =
@@ -203,7 +214,7 @@
                         jvmName = methodSignature.name,
                         descriptor = methodSignature.asString(),
                         flags = flags,
-                        typeArguments = typeParameters.map { it.asKmType() },
+                        typeParameters = typeParameters,
                         parameters = parameters,
                         returnType = returnType,
                         receiverType = receiverType
@@ -324,7 +335,7 @@
                                 name = JvmAbi.computeSetterName(name),
                                 descriptor = setterSignature.asString(),
                                 flags = setterFlags,
-                                typeArguments = emptyList(),
+                                typeParameters = emptyList(),
                                 parameters = listOf(param),
                                 returnType = KM_VOID_TYPE,
                                 receiverType = null,
@@ -337,7 +348,7 @@
                                 name = JvmAbi.computeGetterName(name),
                                 descriptor = getterSignature.asString(),
                                 flags = getterFlags,
-                                typeArguments = emptyList(),
+                                typeParameters = emptyList(),
                                 parameters = emptyList(),
                                 returnType = returnType,
                                 receiverType = null,
@@ -495,12 +506,10 @@
             KmClassTypeInfo(
                 kmType = KmType(
                     flags = flags,
-                    typeArguments = typeParameters.map {
-                        it.asKmType()
-                    },
-                    extendsBound = null,
+                    typeArguments = typeParameters.map(KmTypeParameter::asKmType),
                     isExtensionType = false
                 ),
+                typeParameters = typeParameters,
                 superType = superType
             )
         )
@@ -512,20 +521,20 @@
     private val flags: Flags,
     private val output: (KmTypeParameter) -> Unit
 ) : KmTypeParameterVisitor() {
-    private var upperBound: KmType? = null
+    private var upperBounds: MutableList<KmType> = mutableListOf()
     override fun visitEnd() {
         output(
             KmTypeParameter(
                 name = name,
                 flags = flags,
-                extendsBound = upperBound
+                upperBounds = upperBounds
             )
         )
     }
 
     override fun visitUpperBound(flags: Flags): KmTypeVisitor {
         return TypeReader(flags) {
-            upperBound = it
+            upperBounds.add(it)
         }
     }
 }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElement.kt
index 2d1ae4b..2d32a98 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElement.kt
@@ -44,6 +44,8 @@
         get() = kmType.flags
     val superType
         get() = typeInfo.superType
+    val typeParameters
+        get() = typeInfo.typeParameters
     private val functionList: List<KmFunction> by lazy { classMetadata.readFunctions() }
     private val constructorList: List<KmConstructor> by lazy { classMetadata.readConstructors() }
     private val propertyList: List<KmProperty> by lazy { classMetadata.readProperties() }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspClassFileUtility.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspClassFileUtility.kt
index 4dc85b8..f01c0fd 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspClassFileUtility.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspClassFileUtility.kt
@@ -16,6 +16,7 @@
 
 package androidx.room.compiler.processing.ksp
 
+import androidx.room.compiler.processing.XMethodElement
 import androidx.room.compiler.processing.XProcessingConfig
 import com.google.devtools.ksp.symbol.ClassKind
 import com.google.devtools.ksp.symbol.KSClassDeclaration
@@ -103,8 +104,6 @@
 
     /**
      * Sorts the given methods in the order they are declared in the backing class declaration.
-     * Note that this does not check signatures so ordering might break if there are multiple
-     * methods with the same name.
      */
     fun orderMethods(
         owner: KSClassDeclaration,
@@ -112,14 +111,14 @@
     ): List<KspMethodElement> {
         // no reason to try to load .class if we don't have any fields to sort
         if (methods.isEmpty()) return methods
-        val comparator = getNamesComparator(owner, Type.METHOD, KspMethodElement::jvmName)
+        val comparator = getNamesComparator(owner, Type.METHOD, XMethodElement::jvmDescriptor)
         return if (comparator == null) {
             methods
         } else {
             methods.forEach {
-                // make sure each name gets registered so that if we didn't find it in .class for
-                // whatever reason, we keep the order given from KSP.
-                comparator.register(it.jvmName)
+                // make sure each descriptor gets registered so that if we didn't find it in .class
+                // for whatever reason, we keep the order given from KSP.
+                comparator.register(it.jvmDescriptor())
             }
             methods.sortedWith(comparator)
         }
@@ -157,7 +156,7 @@
             val typeReferences = ReflectionReferences.getInstance(classDeclaration) ?: return null
             val binarySource = getBinarySource(typeReferences, classDeclaration) ?: return null
 
-            val fieldNameComparator = MemberNameComparator(
+            val memberNameComparator = MemberNameComparator(
                 getName = getName,
                 // we can do strict mode only in classes. For Interfaces, private methods won't
                 // show up in the binary.
@@ -168,7 +167,13 @@
                 if (method.name == type.visitorName) {
                     val nameAsString = typeReferences.asStringMethod.invoke(args[0])
                     if (nameAsString is String) {
-                        fieldNameComparator.register(nameAsString)
+                        when (type) {
+                            Type.FIELD -> memberNameComparator.register(nameAsString)
+                            Type.METHOD -> {
+                                val methodTypeDescriptor = args[1]
+                                memberNameComparator.register(nameAsString + methodTypeDescriptor)
+                            }
+                        }
                     }
                 }
                 null
@@ -181,8 +186,8 @@
             )
 
             typeReferences.visitMembersMethod.invoke(binarySource, proxy, null)
-            fieldNameComparator.seal()
-            fieldNameComparator
+            memberNameComparator.seal()
+            memberNameComparator
         } catch (ignored: Throwable) {
             // this is best effort, if it failed, just ignore
             if (XProcessingConfig.STRICT_MODE) {
@@ -306,7 +311,7 @@
          */
         private fun getOrder(name: String) = orders.getOrPut(name) {
             if (sealed && strictMode) {
-                error("expected to find field/method $name but it is non-existent")
+                error("expected to find field/method $name but it is non-existent: $orders")
             }
             nextOrder++
         }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspJvmDescriptorUtils.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspJvmDescriptorUtils.kt
new file mode 100644
index 0000000..0ff7763
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspJvmDescriptorUtils.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.room.compiler.processing.ksp
+
+import androidx.room.compiler.processing.XConstructorElement
+import androidx.room.compiler.processing.XConstructorType
+import androidx.room.compiler.processing.XExecutableType
+import androidx.room.compiler.processing.XFieldElement
+import androidx.room.compiler.processing.XMethodElement
+import androidx.room.compiler.processing.XMethodType
+import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.isArray
+import androidx.room.compiler.processing.isBoolean
+import androidx.room.compiler.processing.isByte
+import androidx.room.compiler.processing.isChar
+import androidx.room.compiler.processing.isDouble
+import androidx.room.compiler.processing.isFloat
+import androidx.room.compiler.processing.isInt
+import androidx.room.compiler.processing.isKotlinUnit
+import androidx.room.compiler.processing.isLong
+import androidx.room.compiler.processing.isShort
+import androidx.room.compiler.processing.isTypeVariable
+import androidx.room.compiler.processing.isVoid
+import androidx.room.compiler.processing.isVoidObject
+
+/**
+ * Returns the method descriptor of this field element.
+ *
+ * For reference, see the [JVM
+ * specification, section 4.3.2](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.2).
+ */
+internal fun XFieldElement.jvmDescriptor() = name + ":" + type.jvmDescriptor()
+
+/**
+ * Returns the method descriptor of this method element.
+ *
+ * For reference, see the [JVM
+ * specification, section 4.3.3](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.3).
+ */
+internal fun XMethodElement.jvmDescriptor() = jvmName + executableType.jvmDescriptor()
+
+/**
+ * Returns the method descriptor of this constructor element.
+ *
+ * For reference, see the [JVM
+ * specification, section 4.3.3](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.3).
+ */
+internal fun XConstructorElement.jvmDescriptor() = name + executableType.jvmDescriptor()
+
+private fun XExecutableType.jvmDescriptor(): String {
+    val parameterTypeDescriptors = parameterTypes.joinToString("") { it.jvmDescriptor() }
+    val returnTypeDescriptor = when (this) {
+        is XMethodType -> returnType.jvmDescriptor()
+        is XConstructorType -> "V"
+        else -> error("Unexpected executable type: $javaClass")
+    }
+    return "($parameterTypeDescriptors)$returnTypeDescriptor"
+}
+
+private fun XType.jvmDescriptor(): String {
+    return when {
+        isKotlinUnit() || isNone() || isVoid() || isVoidObject() -> "V"
+        isArray() -> "[${componentType.jvmDescriptor()}"
+        // See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.2
+        typeElement != null -> "L${typeElement!!.asClassName().reflectionName.replace('.', '/')};"
+        // For a type variable with multiple bounds: "the erasure of a type variable is determined
+        // by the first type in its bound" - JLS Sec 4.4
+        // See https://docs.oracle.com/javase/specs/jls/se16/html/jls-4.html#jls-4.4
+        isTypeVariable() -> upperBounds.first().jvmDescriptor()
+        isInt() -> "I"
+        isLong() -> "J"
+        isByte() -> "B"
+        isShort() -> "S"
+        isDouble() -> "D"
+        isFloat() -> "F"
+        isBoolean() -> "Z"
+        isChar() -> "C"
+        else -> error("Unexpected type: $javaClass")
+    }
+}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
index af44a4d..c452578 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
@@ -253,12 +253,9 @@
         }
         val qName = ksType.declaration.qualifiedName?.asString()
         if (declaration is KSTypeParameter) {
-            return KspTypeArgumentType(
+            return KspTypeVariableType(
                 env = this,
-                typeArg = resolver.getTypeArgument(
-                    ksType.createTypeReference(),
-                    declaration.variance
-                ),
+                ksType = ksType,
                 jvmTypeResolver = null
             )
         }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
index 413bc62..b0d03e1 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
@@ -298,6 +298,4 @@
         }
         return copyWithNullability(XNullability.NONNULL)
     }
-
-    override fun isTypeVariable() = false
 }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt
index a9a4c03..ffacdbd 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeArgumentType.kt
@@ -70,8 +70,6 @@
         return _extendsBound
     }
 
-    override fun isTypeVariable() = true
-
     override fun copyWithNullability(nullability: XNullability): KspTypeArgumentType {
         return KspTypeArgumentType(
             env = env,
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeVariableType.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeVariableType.kt
new file mode 100644
index 0000000..9d91f0c4
--- /dev/null
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspTypeVariableType.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.room.compiler.processing.ksp
+
+import androidx.room.compiler.processing.XNullability
+import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.XTypeVariableType
+import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.KSTypeParameter
+import com.squareup.kotlinpoet.javapoet.JTypeName
+import com.squareup.kotlinpoet.javapoet.KTypeName
+
+internal class KspTypeVariableType(
+    env: KspProcessingEnv,
+    ksType: KSType,
+    jvmTypeResolver: KspJvmTypeResolver?
+) : KspType(
+    env = env,
+    ksType = ksType,
+    jvmTypeResolver = jvmTypeResolver
+), XTypeVariableType {
+    private val typeVariable: KSTypeParameter by lazy {
+        // Note: This is a workaround for a bug in KSP where we may get ERROR_TYPE in the bounds
+        // (https://github.com/google/ksp/issues/1250). To work around it we get the matching
+        // KSTypeParameter from the parent declaration instead.
+        ksType.declaration.parentDeclaration!!.typeParameters
+            .filter { it.name == (ksType.declaration as KSTypeParameter).name }
+            .single()
+    }
+
+    override fun resolveJTypeName(): JTypeName {
+        return typeVariable.asJTypeName(env.resolver)
+    }
+
+    override fun resolveKTypeName(): KTypeName {
+        return typeVariable.asKTypeName(env.resolver)
+    }
+
+    override val upperBounds: List<XType> = typeVariable.bounds.map(env::wrap).toList()
+
+    override fun boxed(): KspTypeVariableType {
+        return this
+    }
+
+    override fun copyWithNullability(nullability: XNullability): KspTypeVariableType {
+        return KspTypeVariableType(
+            env = env,
+            ksType = ksType,
+            jvmTypeResolver = jvmTypeResolver
+        )
+    }
+
+    override fun copyWithJvmTypeResolver(jvmTypeResolver: KspJvmTypeResolver): KspType {
+        return KspTypeVariableType(
+            env = env,
+            ksType = ksType,
+            jvmTypeResolver = jvmTypeResolver
+        )
+    }
+}
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
index be70bab..da53f87 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
@@ -19,6 +19,7 @@
 import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.codegen.asClassName
+import androidx.room.compiler.processing.ksp.jvmDescriptor
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.asKClassName
@@ -1922,6 +1923,43 @@
         }
     }
 
+    @Test
+    fun sameMethodNameOrder() {
+        runTest(
+            sources = listOf(
+                Source.kotlin(
+                    "test.Foo.kt",
+                    """
+                    package test
+                    class Foo<T1: Bar, T2: Baz> {
+                        fun method(): String = TODO()
+                        fun method(param: String): String = TODO()
+                        fun method(param: Any): String = TODO()
+                        fun method(param: T1): T2 = TODO()
+                        fun method(param: T2): T1 = TODO()
+                        fun <U1: Baz, U2> method(param1: U1, param2: U2) {}
+                        fun <U1: Baz, U2: U1> method(param1: U1, param2: U2): T1 = TODO()
+                    }
+                    interface Bar
+                    interface Baz
+                    """.trimIndent()
+                )
+            )
+        ) { invocation ->
+            val foo = invocation.processingEnv.requireTypeElement("test.Foo")
+            assertThat(foo.getDeclaredMethods().map { it.jvmDescriptor() }.toList())
+                .containsExactly(
+                    "method()Ljava/lang/String;",
+                    "method(Ljava/lang/String;)Ljava/lang/String;",
+                    "method(Ljava/lang/Object;)Ljava/lang/String;",
+                    "method(Ltest/Bar;)Ltest/Baz;",
+                    "method(Ltest/Baz;)Ltest/Bar;",
+                    "method(Ltest/Baz;Ljava/lang/Object;)V",
+                    "method(Ltest/Baz;Ltest/Baz;)Ltest/Bar;"
+                ).inOrder()
+        }
+    }
+
     /**
      * it is good to exclude methods coming from Object when testing as they differ between KSP
      * and KAPT but irrelevant for Room.
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElementTest.kt
index f451bd8..0d99b43 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/javac/kotlin/KotlinMetadataElementTest.kt
@@ -681,8 +681,12 @@
 
             val (_, withUpperBounds) = getMetadataElement(invocation, "WithUpperBounds")
             assertThat(withUpperBounds.kmType.typeArguments).hasSize(2)
-            assertThat(withUpperBounds.kmType.typeArguments[0].extendsBound?.isNullable()).isFalse()
-            assertThat(withUpperBounds.kmType.typeArguments[1].extendsBound?.isNullable()).isTrue()
+            assertThat(withUpperBounds.kmType.typeArguments[0].upperBounds).hasSize(1)
+            assertThat(withUpperBounds.kmType.typeArguments[0].upperBounds!![0].isNullable())
+                .isFalse()
+            assertThat(withUpperBounds.kmType.typeArguments[1].upperBounds).hasSize(1)
+            assertThat(withUpperBounds.kmType.typeArguments[1].upperBounds!![0].isNullable())
+                .isTrue()
 
             val (_, withSuperType) = getMetadataElement(invocation, "WithSuperType")
             assertThat(withSuperType.superType?.typeArguments?.get(0)?.isNullable()).isFalse()
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspJvmDescriptorUtilsTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspJvmDescriptorUtilsTest.kt
new file mode 100644
index 0000000..f8e5914
--- /dev/null
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspJvmDescriptorUtilsTest.kt
@@ -0,0 +1,509 @@
+/*
+ * 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.room.compiler.processing.ksp
+
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.isConstructor
+import androidx.room.compiler.processing.isField
+import androidx.room.compiler.processing.isMethod
+import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.runProcessorTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class KspJvmDescriptorUtilsTest {
+    private val describeAnnotation =
+        Source.java(
+            "androidx.room.test.Describe",
+            """
+            package androidx.room.test;
+
+            import java.lang.annotation.ElementType;
+            import java.lang.annotation.Target;
+
+            @Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
+            public @interface Describe { }
+            """)
+
+    @Test
+    fun descriptor_method_simple() {
+        fun checkSources(vararg sources: Source) {
+            runTest(sources = sources) { invocation ->
+                assertThat(invocation.annotatedElements().map(this::descriptor))
+                    .containsExactly("emptyMethod()V")
+            }
+        }
+        checkSources(
+            Source.java(
+                "androidx.room.test.DummyClass",
+                """
+                package androidx.room.test;
+                public class DummyClass {
+                    @Describe public void emptyMethod() {}
+                }
+                """)
+        )
+        checkSources(
+            Source.kotlin(
+                "androidx.room.test.DummyClass.kt",
+                """
+                package androidx.room.test
+                class DummyClass {
+                    @Describe fun emptyMethod() {}
+                }
+                """)
+        )
+    }
+
+    @Test
+    fun descriptor_field() {
+        fun checkSources(vararg sources: Source) {
+            runTest(sources = sources) { invocation ->
+                assertThat(invocation.annotatedElements().map(this::descriptor)).containsExactly(
+                    "field1:I",
+                    "field2:Ljava/lang/String;",
+                    "field3:Ljava/lang/Object;",
+                    "field4:Ljava/util/List;"
+                )
+            }
+        }
+        checkSources(
+            Source.java(
+                "androidx.room.test.DummyClass",
+                """
+                package androidx.room.test;
+
+                import java.util.List;
+
+                class DummyClass<T> {
+                    @Describe int field1;
+                    @Describe String field2;
+                    @Describe T field3;
+                    @Describe List<String> field4;
+                }
+                """)
+        )
+        checkSources(
+            Source.kotlin(
+                "androidx.room.test.DummyClass.kt",
+                """
+                package androidx.room.test
+                class DummyClass<T> {
+                    @Describe val field1: Int = TODO()
+                    @Describe val field2: String = TODO()
+                    @Describe val field3: T = TODO()
+                    @Describe val field4: List<String> = TODO()
+                }
+                """)
+        )
+    }
+
+    @Test
+    fun descriptor_method_erasured() {
+        fun checkSources(vararg sources: Source) {
+            runTest(sources = sources) { invocation ->
+                assertThat(invocation.annotatedElements().map(this::descriptor)).containsAtLeast(
+                    "method1(Landroidx/room/test/Foo;)V",
+                    "method2()Landroidx/room/test/Foo;",
+                    "method3()Ljava/util/List;",
+                    "method4()Ljava/util/Map;",
+                    "method5()Ljava/util/ArrayList;",
+                    "method6(Ljava/lang/Object;)Landroidx/room/test/Foo;",
+                    "method7(Ljava/lang/Object;)Ljava/lang/Object;",
+                    "method8(Ljava/lang/Object;)Ljava/lang/String;",
+                    "method9(Landroidx/room/test/Foo;)Landroidx/room/test/Foo;",
+                    "method10()Ljava/util/Collection;",
+                    "method11()Landroidx/room/test/Foo;",
+                )
+            }
+        }
+        checkSources(
+            Source.java(
+                "androidx.room.test.DummyClass",
+                """
+                package androidx.room.test;
+
+                import java.util.ArrayList;
+                import java.util.Collection;
+                import java.util.List;
+                import java.util.Map;
+
+                class DummyClass<T extends Foo> {
+                    @Describe void method1(T param) { }
+                    @Describe T method2() { return null; }
+                    @Describe List<? extends String> method3() { return null; }
+                    @Describe Map<T, String> method4() { return null; }
+                    @Describe ArrayList<Map<T, String>> method5() { return null; }
+                    @Describe <I, O extends T> O method6(I param) { return null; }
+                    @Describe static <I, O extends I> O method7(I param) { return null; }
+                    @Describe static <I, O extends String> O method8(I param) { return null; }
+                    @Describe
+                    static <I extends Foo, O extends I> O method9(I param) { return null; }
+                    @Describe static <P extends Collection & Foo> P method10() { return null; }
+                    @Describe static <P extends Foo & Collection<?>> P method11() { return null; }
+                }
+                interface Foo {}
+                """)
+        )
+        checkSources(
+            Source.kotlin(
+                "androidx.room.test.DummyClass.kt",
+                """
+                package androidx.room.test
+                class DummyClass<T: Foo> {
+                    @Describe fun method1(param: T) {}
+                    @Describe fun method2(): T = TODO()
+                    @Describe fun method3(): List<out String> = TODO()
+                    @Describe fun method4(): Map<T, String> = TODO()
+                    @Describe fun method5(): ArrayList<Map<T, String>> = TODO()
+                    @Describe fun <I, O: T> method6(param: I): O = TODO()
+                    companion object {
+                        @Describe fun <I, O : I> method7(param: I): O  = TODO()
+                        @Describe fun <I, O: String> method8(param: I): O = TODO()
+                        @Describe fun <I: Foo, O: I> method9(param: I): O  = TODO()
+                        @Describe fun <P> method10(): P where P: Collection<*>, P: Foo = TODO()
+                        @Describe fun <P> method11(): P where P: Foo, P: Collection<*> = TODO()
+                    }
+                }
+                interface Foo
+                """)
+        )
+    }
+
+    @Test
+    fun descriptor_class_erasured() {
+        fun checkSources(vararg sources: Source) {
+            runTest(sources = sources) { invocation ->
+                assertThat(invocation.annotatedElements().map(this::descriptor)).containsExactly(
+                    "method1(Ljava/lang/Object;)Ljava/lang/Object;",
+                    "method2(Ljava/lang/Object;)Ljava/lang/String;",
+                    "method3(Ljava/lang/Object;)Ljava/lang/String;",
+                    "method4(Ljava/lang/Object;)Ljava/lang/Object;",
+                    "method5(Landroidx/room/test/Outer\$Foo;)Landroidx/room/test/Outer\$Foo;",
+                    "method6(Landroidx/room/test/Outer\$Bar;)Landroidx/room/test/Outer\$Bar;",
+                )
+            }
+        }
+        checkSources(
+            Source.java(
+                "androidx.room.test.Outer",
+                """
+                package androidx.room.test;
+                class Outer {
+                    class MyClass1<I, O extends I> {
+                        @Describe O method1(I input) { return null; }
+                        @Describe static <I, O extends String> O method2(I input) { return null; }
+                    }
+                    class MyClass2<I, O extends String> {
+                        @Describe O method3(I input) { return null; }
+                        @Describe static <I, O extends I> O method4(I input) { return null; }
+                    }
+                    class MyClass3<I extends Foo, O extends I> {
+                        @Describe O method5(I input) { return null; }
+                        @Describe static <I extends Bar, O extends I> O method6(I input) {
+                         return null;
+                        }
+                    }
+                    class Foo {}
+                    class Bar {}
+                }
+                """)
+        )
+        checkSources(
+            Source.kotlin(
+                "androidx.room.test.Outer.kt",
+                """
+                package androidx.room.test
+                class Outer {
+                    class MyClass1<I, O: I> {
+                        @Describe fun method1(input: I): O = TODO()
+                        companion object {
+                            @Describe fun <I, O: String> method2(input: I): O = TODO()
+                        }
+                    }
+                    class MyClass2<I, O: String> {
+                        @Describe fun method3(input: I): O = TODO()
+                        companion object {
+                            @Describe fun <I, O: I> method4(input: I): O = TODO()
+                        }
+                    }
+                    class MyClass3<I: Foo, O: I> {
+                        @Describe fun method5(input: I): O = TODO()
+                        companion object {
+                            @Describe fun <I: Bar, O: I> method6(input: I): O = TODO()
+                        }
+                    }
+                    class Foo
+                    class Bar
+                }
+                """)
+        )
+    }
+
+    @Test
+    fun descriptor_method_primitiveParams() {
+        fun checkSources(vararg sources: Source) {
+            runTest(sources = sources) { invocation ->
+                assertThat(invocation.annotatedElements().map(this::descriptor)).containsExactly(
+                    "method1(ZI)V",
+                    "method2(C)B",
+                    "method3(DF)V",
+                    "method4(JS)V"
+                )
+            }
+        }
+        checkSources(
+            Source.java(
+                "androidx.room.test.DummyClass",
+                """
+                package androidx.room.test;
+                class DummyClass {
+                    @Describe void method1(boolean yesOrNo, int number) { }
+                    @Describe byte method2(char letter) { return 0; }
+                    @Describe void method3(double realNumber1, float realNumber2) { }
+                    @Describe void method4(long bigNumber, short littlerNumber) { }
+                }
+                """)
+        )
+        checkSources(
+            Source.kotlin(
+                "androidx.room.test.DummyClass.kt",
+                """
+                package androidx.room.test
+                class DummyClass {
+                    @Describe fun method1(yesOrNo: Boolean, number: Int) {}
+                    @Describe fun method2(letter: Char): Byte = TODO()
+                    @Describe fun method3(realNumber1: Double, realNumber2: Float) {}
+                    @Describe fun method4(bigNumber: Long, littlerNumber: Short) {}
+                }
+                """)
+        )
+    }
+
+    @Test
+    fun descriptor_method_classParam_javaTypes() {
+        fun checkSources(vararg sources: Source) {
+            runTest(sources = sources) { invocation ->
+                assertThat(invocation.annotatedElements().map(this::descriptor)).containsExactly(
+                    "method1(Ljava/lang/Object;)V",
+                    "method2()Ljava/lang/Object;",
+                    "method3(Ljava/util/ArrayList;)Ljava/util/List;",
+                    "method4()Ljava/util/Map;"
+                )
+            }
+        }
+        checkSources(
+            Source.java(
+                "androidx.room.test.DummyClass",
+                """
+                package androidx.room.test;
+
+                import java.util.ArrayList;
+                import java.util.List;
+                import java.util.Map;
+
+                class DummyClass {
+                    @Describe void method1(Object something) { }
+                    @Describe Object method2() { return null; }
+                    @Describe List<String> method3(ArrayList<Integer> list) { return null; }
+                    @Describe Map<String, Object> method4() { return null; }
+                }
+                """)
+        )
+        checkSources(
+            Source.kotlin(
+                "androidx.room.test.DummyClass.kt",
+                """
+                package androidx.room.test;
+                class DummyClass {
+                    @Describe fun method1(something: Object) {}
+                    @Describe fun method2(): Object = TODO()
+                    @Describe fun method3(list: ArrayList<Integer>): List<String> = TODO()
+                    @Describe fun method4(): Map<String, Object> = TODO()
+                }
+                """)
+        )
+    }
+
+    @Test
+    fun descriptor_method_classParam_testClass() {
+        fun checkSources(vararg sources: Source) {
+            runTest(sources = sources) { invocation ->
+                assertThat(invocation.annotatedElements().map(this::descriptor)).containsExactly(
+                    "method1(Landroidx/room/test/DataClass;)V",
+                    "method2()Landroidx/room/test/DataClass;"
+                )
+            }
+        }
+        checkSources(
+            Source.java(
+                "androidx.room.test.DataClass",
+                """
+                package androidx.room.test;
+                class DataClass {}
+                """),
+            Source.java(
+                "androidx.room.test.DummyClass",
+                """
+                package androidx.room.test;
+                class DummyClass {
+                    @Describe void method1(DataClass data) { }
+                    @Describe DataClass method2() { return null; }
+                }
+                """),
+        )
+        checkSources(
+            Source.kotlin(
+                "androidx.room.test.DummyClass.kt",
+                """
+                package androidx.room.test;
+                class DummyClass {
+                    @Describe fun method1(data: DataClass) {}
+                    @Describe fun method2(): DataClass = TODO()
+                }
+                class DataClass
+                """),
+        )
+    }
+
+    @Test
+    fun descriptor_method_classParam_innerTestClass() {
+        fun checkSources(vararg sources: Source) {
+            runTest(sources = sources) { invocation ->
+                assertThat(invocation.annotatedElements().map(this::descriptor)).containsExactly(
+                    "method1(Landroidx/room/test/DataClass\$MemberInnerData;)V",
+                    "method2(Landroidx/room/test/DataClass\$StaticInnerData;)V",
+                    "method3(Landroidx/room/test/DataClass\$EnumData;)V",
+                    "method4()Landroidx/room/test/DataClass\$StaticInnerData;"
+                )
+            }
+        }
+        checkSources(
+            Source.java(
+                "androidx.room.test.DataClass",
+                """
+                package androidx.room.test;
+                class DataClass {
+                    class MemberInnerData { }
+                    static class StaticInnerData { }
+                    enum EnumData { VALUE1, VALUE2 }
+                }
+                """
+            ),
+            Source.java(
+                "androidx.room.test.DummyClass",
+                """
+                package androidx.room.test;
+                class DummyClass {
+                    @Describe void method1(DataClass.MemberInnerData data) { }
+                    @Describe void method2(DataClass.StaticInnerData data) { }
+                    @Describe void method3(DataClass.EnumData enumData) { }
+                    @Describe DataClass.StaticInnerData method4() { return null; }
+                }
+                """),
+        )
+        checkSources(
+            Source.kotlin(
+                "androidx.room.test.DummyClass.kt",
+                """
+                package androidx.room.test
+                class DummyClass {
+                    @Describe fun method1(data: DataClass.MemberInnerData) {}
+                    @Describe fun method2(data: DataClass.StaticInnerData) {}
+                    @Describe fun method3(enumData: DataClass.EnumData) {}
+                    @Describe fun method4(): DataClass.StaticInnerData = TODO()
+                }
+                class DataClass {
+                    inner class MemberInnerData
+                    class StaticInnerData
+                    enum class EnumData { VALUE1, VALUE2 }
+                }
+                """),
+        )
+    }
+
+    @Test
+    fun descriptor_method_arrayParams() {
+        fun checkSources(vararg sources: Source) {
+            runTest(sources = sources) { invocation ->
+                assertThat(invocation.annotatedElements().map(this::descriptor)).containsExactly(
+                    "method1([Landroidx/room/test/DataClass;)V",
+                    "method2()[Landroidx/room/test/DataClass;",
+                    "method3([I)V",
+                    "method4([I)V"
+                )
+            }
+        }
+        checkSources(
+            Source.java(
+                "androidx.room.test.DataClass",
+                """
+                package androidx.room.test;
+                class DataClass {}
+                """),
+            Source.java(
+                "androidx.room.test.DummyClass",
+                """
+                package androidx.room.test;
+                class DummyClass {
+                    @Describe void method1(DataClass[] data) { }
+                    @Describe DataClass[] method2() { return null; }
+                    @Describe void method3(int[] array) { }
+                    @Describe void method4(int... array) { }
+                }
+                """),
+        )
+        checkSources(
+            Source.kotlin(
+                "androidx.room.test.DummyClass.kt",
+                """
+                package androidx.room.test;
+                class DummyClass {
+                    @Describe fun method1(data: Array<DataClass>) {}
+                    @Describe fun method2(): Array<DataClass> = TODO()
+                    @Describe fun method3(array: IntArray) {}
+                    @Describe fun method4(vararg array: Int) {}
+                }
+                class DataClass
+                """),
+        )
+    }
+
+    private fun runTest(vararg sources: Source, handler: (XTestInvocation) -> Unit) {
+        runProcessorTest(
+            sources = sources.toList() + describeAnnotation,
+            handler = handler
+        )
+    }
+
+    private fun XTestInvocation.annotatedElements(): Set<XElement> {
+        return roundEnv.getElementsAnnotatedWith("androidx.room.test.Describe")
+    }
+
+    private fun descriptor(element: XElement): String {
+        return when {
+            element.isField() -> element.jvmDescriptor()
+            element.isMethod() -> element.jvmDescriptor()
+            element.isConstructor() -> element.jvmDescriptor()
+            else -> error("Unsupported element to describe.")
+        }
+    }
+}
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
index ef0ec14..35cf851 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
@@ -19,19 +19,23 @@
 import androidx.room.compiler.codegen.XClassName
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.codegen.asClassName
+import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XNullability.NONNULL
 import androidx.room.compiler.processing.XNullability.NULLABLE
 import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.isByte
 import androidx.room.compiler.processing.isInt
 import androidx.room.compiler.processing.isLong
 import androidx.room.compiler.processing.isVoid
 import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.getDeclaredField
 import androidx.room.compiler.processing.util.getField
 import androidx.room.compiler.processing.util.getMethodByJvmName
 import androidx.room.compiler.processing.util.runKspTest
 import androidx.room.compiler.processing.util.runProcessorTest
 import com.google.common.truth.Truth.assertThat
+import com.squareup.kotlinpoet.TypeVariableName
 import com.squareup.kotlinpoet.UNIT
 import com.squareup.kotlinpoet.javapoet.JClassName
 import com.squareup.kotlinpoet.javapoet.JTypeName
@@ -477,50 +481,97 @@
 
     @Suppress("MapGetWithNotNullAssertionOperator")
     @Test
-    fun extendsBounds() {
-        val src = Source.kotlin(
-            "foo.kt",
-            """
-            open class Foo
-            class Bar<T : Foo> {
-            }
-            class Bar_NullableFoo<T : Foo?>
-            """.trimIndent()
-        )
+    fun upperBounds() {
         runProcessorTest(
-            listOf(src)
+            listOf(
+                Source.kotlin(
+                    "Foo.kt",
+                    """
+                    open class Foo
+                    class Bar<T : Foo>
+                    class Bar_NullableFoo<T : Foo?>
+                    """.trimIndent()
+                )
+            )
         ) { invocation ->
-            val classNames = listOf("Bar", "Bar_NullableFoo")
-            val typeArgs = classNames.associateWith { className ->
-                invocation.processingEnv
-                    .requireType(className)
-                    .typeArguments
-                    .single()
+            fun checkTypeElement(
+                typeElement: XTypeElement,
+                typeArgumentJClassName: JTypeVariableName,
+                typeArgumentKClassName: TypeVariableName,
+                nullability: XNullability,
+            ) {
+                val typeParameter = typeElement.typeParameters.single()
+                assertThat(typeParameter.typeVariableName).isEqualTo(typeArgumentJClassName)
+
+                val typeArgument = typeElement.type.typeArguments.single()
+                assertThat(typeArgument.asTypeName().java).isEqualTo(typeArgumentJClassName)
+                if (invocation.isKsp) {
+                    assertThat(typeArgument.asTypeName().kotlin).isEqualTo(typeArgumentKClassName)
+                }
+                assertThat(typeArgument.nullability).isEqualTo(nullability)
             }
-            assertThat(typeArgs["Bar"]!!.asTypeName().java)
-                .isEqualTo(
-                    JTypeVariableName.get("T", JClassName.get("", "Foo"))
+            checkTypeElement(
+                typeElement = invocation.processingEnv.requireTypeElement("Bar"),
+                typeArgumentJClassName = JTypeVariableName.get("T", JClassName.get("", "Foo")),
+                typeArgumentKClassName = KTypeVariableName("T", KClassName("", "Foo")),
+                nullability = NONNULL
+            )
+            checkTypeElement(
+                typeElement = invocation.processingEnv.requireTypeElement("Bar_NullableFoo"),
+                typeArgumentJClassName = JTypeVariableName.get("T", JClassName.get("", "Foo")),
+                typeArgumentKClassName = KTypeVariableName(
+                    "T", KClassName("", "Foo").copy(nullable = true)
+                ),
+                nullability = NULLABLE
+            )
+        }
+    }
+
+    @Suppress("MapGetWithNotNullAssertionOperator")
+    @Test
+    fun extendsBounds() {
+        runProcessorTest(
+            listOf(
+                Source.kotlin(
+                    "Foo.kt",
+                    """
+                    class Foo {
+                        val barFoo: Bar<out Foo> = TODO()
+                        val barNullableFoo: Bar<out Foo?> = TODO()
+                    }
+                    class Bar<T>
+                    """.trimIndent()
                 )
-            if (invocation.isKsp) {
-                assertThat(typeArgs["Bar"]!!.asTypeName().kotlin)
-                    .isEqualTo(
-                        KTypeVariableName("T", KClassName("", "Foo"))
-                    )
+            )
+        ) { invocation ->
+            fun checkType(
+                type: XType,
+                typeArgumentJClassName: JWildcardTypeName,
+                typeArgumentKClassName: KWildcardTypeName,
+                nullability: XNullability,
+            ) {
+                val typeArgument = type.typeArguments.single()
+                assertThat(typeArgument.asTypeName().java).isEqualTo(typeArgumentJClassName)
+                if (invocation.isKsp) {
+                    assertThat(typeArgument.asTypeName().kotlin).isEqualTo(typeArgumentKClassName)
+                }
+                assertThat(typeArgument.nullability).isEqualTo(nullability)
             }
-            assertThat(typeArgs["Bar"]!!.nullability).isEqualTo(NONNULL)
-            assertThat(typeArgs["Bar_NullableFoo"]!!.asTypeName().java)
-                .isEqualTo(
-                    JTypeVariableName.get("T", JClassName.get("", "Foo"))
-                )
-            if (invocation.isKsp) {
-                assertThat(typeArgs["Bar_NullableFoo"]!!.asTypeName().kotlin)
-                    .isEqualTo(
-                        KTypeVariableName(
-                            "T", KClassName("", "Foo").copy(nullable = true)
-                        )
-                    )
-            }
-            assertThat(typeArgs["Bar_NullableFoo"]!!.nullability).isEqualTo(NULLABLE)
+            val foo = invocation.processingEnv.requireTypeElement("Foo")
+            checkType(
+                type = foo.getDeclaredField("barFoo").type,
+                typeArgumentJClassName = JWildcardTypeName.subtypeOf(JClassName.get("", "Foo")),
+                typeArgumentKClassName = KWildcardTypeName.producerOf(KClassName("", "Foo")),
+                nullability = NONNULL
+            )
+            checkType(
+                type = foo.getDeclaredField("barNullableFoo").type,
+                typeArgumentJClassName = JWildcardTypeName.subtypeOf(JClassName.get("", "Foo")),
+                typeArgumentKClassName = KWildcardTypeName.producerOf(
+                    KClassName("", "Foo").copy(nullable = true)
+                ),
+                nullability = NULLABLE
+            )
         }
     }
 
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/preconditions/Checks.kt b/room/room-compiler/src/main/kotlin/androidx/room/preconditions/Checks.kt
index f91c0b4..b57d3fd 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/preconditions/Checks.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/preconditions/Checks.kt
@@ -18,6 +18,7 @@
 
 import androidx.room.compiler.processing.XElement
 import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.isTypeVariable
 import androidx.room.log.RLog
 import kotlin.contracts.contract
 import kotlin.reflect.KClass
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
index 639ba0d..3dae9d4 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
@@ -1681,13 +1681,14 @@
     @Test
     fun dataClass_primaryConstructor() {
         listOf(
-            TestData.AllDefaultVals::class.java.canonicalName!!,
-            TestData.AllDefaultVars::class.java.canonicalName!!,
-            TestData.SomeDefaultVals::class.java.canonicalName!!,
-            TestData.SomeDefaultVars::class.java.canonicalName!!,
-            TestData.WithJvmOverloads::class.java.canonicalName!!
+            "foo.bar.TestData.AllDefaultVals",
+            "foo.bar.TestData.AllDefaultVars",
+            "foo.bar.TestData.SomeDefaultVals",
+            "foo.bar.TestData.SomeDefaultVars",
+            "foo.bar.TestData.WithJvmOverloads"
         ).forEach {
-            runProcessorTest { invocation ->
+            runProcessorTest(sources = listOf(TEST_DATA)) {
+                    invocation ->
                 PojoProcessor.createFor(
                     context = invocation.context,
                     element = invocation.processingEnv.requireTypeElement(it),
@@ -1703,11 +1704,11 @@
 
     @Test
     fun dataClass_withJvmOverloads_primaryConstructor() {
-        runProcessorTest { invocation ->
+        runProcessorTest(sources = listOf(TEST_DATA)) { invocation ->
             PojoProcessor.createFor(
                 context = invocation.context,
                 element = invocation.processingEnv.requireTypeElement(
-                    TestData.WithJvmOverloads::class
+                    "foo.bar.TestData.WithJvmOverloads"
                 ),
                 bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
                 parent = null
@@ -2142,9 +2143,9 @@
     @Test
     fun embedded_nullability() {
         listOf(
-            TestData.SomeEmbeddedVals::class.java.canonicalName!!
+            "foo.bar.TestData.SomeEmbeddedVals"
         ).forEach {
-            runProcessorTest { invocation ->
+            runProcessorTest(sources = listOf(TEST_DATA)) { invocation ->
                 val result = PojoProcessor.createFor(
                     context = invocation.context,
                     element = invocation.processingEnv.requireTypeElement(it),
@@ -2206,48 +2207,47 @@
     }
 
     // Kotlin data classes to verify the PojoProcessor.
-    private class TestData {
-        data class AllDefaultVals(
-            val name: String = "",
-            val number: Int = 0,
-            val bit: Boolean = false
-        )
-
-        data class AllDefaultVars(
-            var name: String = "",
-            var number: Int = 0,
-            var bit: Boolean = false
-        )
-
-        data class SomeDefaultVals(
-            val name: String,
-            val number: Int = 0,
-            val bit: Boolean
-        )
-
-        data class SomeDefaultVars(
-            var name: String,
-            var number: Int = 0,
-            var bit: Boolean
-        )
-
-        data class WithJvmOverloads @JvmOverloads constructor(
-            val name: String,
-            val lastName: String = "",
-            var number: Int = 0,
-            var bit: Boolean
-        )
-
-        data class AllNullableVals(
-            val name: String?,
-            val number: Int?,
-            val bit: Boolean?
-        )
-
-        data class SomeEmbeddedVals(
-            val id: String,
-            @Embedded(prefix = "non_nullable_") val nonNullableVal: AllNullableVals,
-            @Embedded(prefix = "nullable_") val nullableVal: AllNullableVals?
-        )
-    }
-}
+    private val TEST_DATA = Source.kotlin("TestData.kt", """
+        package foo.bar
+        import ${Embedded::class.java.canonicalName}
+        private class TestData {
+            data class AllDefaultVals(
+                val name: String = "",
+                val number: Int = 0,
+                val bit: Boolean = false
+            )
+            data class AllDefaultVars(
+                var name: String = "",
+                var number: Int = 0,
+                var bit: Boolean = false
+            )
+            data class SomeDefaultVals(
+                val name: String,
+                val number: Int = 0,
+                val bit: Boolean
+            )
+            data class SomeDefaultVars(
+                var name: String,
+                var number: Int = 0,
+                var bit: Boolean
+            )
+            data class WithJvmOverloads @JvmOverloads constructor(
+                val name: String,
+                val lastName: String = "",
+                var number: Int = 0,
+                var bit: Boolean
+            )
+            data class AllNullableVals(
+                val name: String?,
+                val number: Int?,
+                val bit: Boolean?
+            )
+            data class SomeEmbeddedVals(
+                val id: String,
+                @Embedded(prefix = "non_nullable_") val nonNullableVal: AllNullableVals,
+                @Embedded(prefix = "nullable_") val nullableVal: AllNullableVals?
+            )
+        }
+        """
+    )
+}
\ No newline at end of file
diff --git a/room/room-ktx/src/main/java/androidx/room/CoroutinesRoom.kt b/room/room-ktx/src/main/java/androidx/room/CoroutinesRoom.kt
index 1495051e..69c458d 100644
--- a/room/room-ktx/src/main/java/androidx/room/CoroutinesRoom.kt
+++ b/room/room-ktx/src/main/java/androidx/room/CoroutinesRoom.kt
@@ -53,7 +53,7 @@
             inTransaction: Boolean,
             callable: Callable<R>
         ): R {
-            if (db.isOpen && db.inTransaction()) {
+            if (db.isOpenInternal && db.inTransaction()) {
                 return callable.call()
             }
 
@@ -74,7 +74,7 @@
             cancellationSignal: CancellationSignal?,
             callable: Callable<R>
         ): R {
-            if (db.isOpen && db.inTransaction()) {
+            if (db.isOpenInternal && db.inTransaction()) {
                 return callable.call()
             }
 
diff --git a/room/room-runtime/src/main/java/androidx/room/InvalidationTracker.kt b/room/room-runtime/src/main/java/androidx/room/InvalidationTracker.kt
index 251b82a..2088242 100644
--- a/room/room-runtime/src/main/java/androidx/room/InvalidationTracker.kt
+++ b/room/room-runtime/src/main/java/androidx/room/InvalidationTracker.kt
@@ -162,11 +162,11 @@
         }
     }
 
-    // TODO: Close CleanupStatement
-    internal fun onAutoCloseCallback() {
+    private fun onAutoCloseCallback() {
         synchronized(trackerLock) {
             initialized = false
             observedTableTracker.resetTriggerState()
+            cleanupStatement?.close()
         }
     }
 
@@ -329,7 +329,7 @@
     }
 
     internal fun ensureInitialization(): Boolean {
-        if (!database.isOpen) {
+        if (!database.isOpenInternal) {
             return false
         }
         if (!initialized) {
@@ -526,7 +526,7 @@
      * This api should eventually be public.
      */
     internal fun syncTriggers() {
-        if (!database.isOpen) {
+        if (!database.isOpenInternal) {
             return
         }
         syncTriggers(database.openHelper.writableDatabase)
diff --git a/room/room-runtime/src/main/java/androidx/room/RoomDatabase.kt b/room/room-runtime/src/main/java/androidx/room/RoomDatabase.kt
index 599d9d3..a455992 100644
--- a/room/room-runtime/src/main/java/androidx/room/RoomDatabase.kt
+++ b/room/room-runtime/src/main/java/androidx/room/RoomDatabase.kt
@@ -394,13 +394,22 @@
     /**
      * True if database connection is open and initialized.
      *
+     * When Room is configured with [RoomDatabase.Builder.setAutoCloseTimeout] the database
+     * is considered open even if internally the connection has been closed, unless manually closed.
+     *
      * @return true if the database connection is open, false otherwise.
      */
-    @Suppress("Deprecation")
+    @Suppress("Deprecation") // Due to usage of `mDatabase`
     open val isOpen: Boolean
-        get() {
-            return (autoCloser?.isActive ?: mDatabase?.isOpen) == true
-        }
+        get() = (autoCloser?.isActive ?: mDatabase?.isOpen) == true
+
+    /**
+     * True if the actual database connection is open, regardless of auto-close.
+     */
+    @Suppress("Deprecation") // Due to usage of `mDatabase`
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    val isOpenInternal: Boolean
+        get() = mDatabase?.isOpen == true
 
     /**
      * Closes the database if it is already open.
@@ -1193,7 +1202,7 @@
 
         /**
          * Enables auto-closing for the database to free up unused resources. The underlying
-         * database will be closed after it's last use after the specified `autoCloseTimeout` has
+         * database will be closed after it's last use after the specified [autoCloseTimeout] has
          * elapsed since its last usage. The database will be automatically
          * re-opened the next time it is accessed.
          *
@@ -1210,7 +1219,7 @@
          *
          * The auto-closing database operation runs on the query executor.
          *
-         * The database will not be reopened if the RoomDatabase or the
+         * The database will not be re-opened if the RoomDatabase or the
          * SupportSqliteOpenHelper is closed manually (by calling
          * [RoomDatabase.close] or [SupportSQLiteOpenHelper.close]. If the
          * database is closed manually, you must create a new database using
diff --git a/room/room-runtime/src/test/java/androidx/room/InvalidationTrackerTest.kt b/room/room-runtime/src/test/java/androidx/room/InvalidationTrackerTest.kt
index 5f2c9aa..060110e 100644
--- a/room/room-runtime/src/test/java/androidx/room/InvalidationTrackerTest.kt
+++ b/room/room-runtime/src/test/java/androidx/room/InvalidationTrackerTest.kt
@@ -77,7 +77,7 @@
         doReturn(statement).whenever(mSqliteDb)
             .compileStatement(eq(InvalidationTracker.RESET_UPDATED_TABLES_SQL))
         doReturn(mSqliteDb).whenever(mOpenHelper).writableDatabase
-        doReturn(true).whenever(mRoomDatabase).isOpen
+        doReturn(true).whenever(mRoomDatabase).isOpenInternal
         doReturn(ArchTaskExecutor.getIOThreadExecutor()).whenever(mRoomDatabase).queryExecutor
         val closeLock = ReentrantLock()
         doReturn(closeLock).whenever(mRoomDatabase).getCloseLock()
@@ -247,7 +247,7 @@
 
     @Test
     fun closedDb() {
-        doReturn(false).whenever(mRoomDatabase).isOpen
+        doReturn(false).whenever(mRoomDatabase).isOpenInternal
         doThrow(IllegalStateException("foo")).whenever(mOpenHelper).writableDatabase
         mTracker.addObserver(LatchObserver(1, "a", "b"))
         mTracker.refreshRunnable.run()
diff --git a/settings.gradle b/settings.gradle
index cf57a94..62f973f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -579,6 +579,7 @@
 includeProject(":concurrent:concurrent-futures", [BuildType.MAIN, BuildType.CAMERA, BuildType.COMPOSE])
 includeProject(":concurrent:concurrent-futures-ktx", [BuildType.MAIN, BuildType.CAMERA])
 includeProject(":constraintlayout:constraintlayout-compose", [BuildType.COMPOSE])
+includeProject(":constraintlayout:constraintlayout-compose-lint", [BuildType.COMPOSE])
 includeProject(":constraintlayout:constraintlayout-compose:integration-tests:constraintlayout-compose-demos", [BuildType.COMPOSE])
 includeProject(":constraintlayout:constraintlayout-compose:integration-tests:macrobenchmark", [BuildType.COMPOSE])
 includeProject(":constraintlayout:constraintlayout-compose:integration-tests:macrobenchmark-target", [BuildType.COMPOSE])
@@ -911,6 +912,7 @@
 includeProject(":wear:benchmark:integration-tests:macrobenchmark-target", [BuildType.MAIN, BuildType.COMPOSE])
 includeProject(":wear:benchmark:integration-tests:macrobenchmark", [BuildType.MAIN, BuildType.COMPOSE])
 includeProject(":wear:compose:compose-foundation", [BuildType.COMPOSE])
+includeProject(":wear:compose:compose-foundation-benchmark", "wear/compose/compose-foundation/benchmark", [BuildType.COMPOSE])
 includeProject(":wear:compose:compose-foundation-samples", "wear/compose/compose-foundation/samples", [BuildType.COMPOSE])
 includeProject(":wear:compose:compose-material", [BuildType.COMPOSE])
 includeProject(":wear:compose:compose-material3", [BuildType.COMPOSE])
@@ -959,6 +961,8 @@
 includeProject(":wear:watchface:watchface-samples-minimal-instances", [BuildType.MAIN, BuildType.WEAR])
 includeProject(":wear:watchface:watchface-samples-minimal-style", [BuildType.MAIN, BuildType.WEAR])
 includeProject(":wear:watchface:watchface-style", [BuildType.MAIN, BuildType.WEAR])
+includeProject(":wear:watchface:watchface-style-old-api-test-service", "wear/watchface/watchface-style/old-api-test-service", [BuildType.MAIN, BuildType.WEAR])
+includeProject(":wear:watchface:watchface-style-old-api-test-stub", "wear/watchface/watchface-style/old-api-test-stub", [BuildType.MAIN, BuildType.WEAR])
 includeProject(":webkit:integration-tests:testapp", [BuildType.MAIN])
 includeProject(":webkit:webkit", [BuildType.MAIN])
 includeProject(":window:window", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN, BuildType.CAMERA])
diff --git a/slice/slice-view/build.gradle b/slice/slice-view/build.gradle
index 8d7bd57..1df018c 100644
--- a/slice/slice-view/build.gradle
+++ b/slice/slice-view/build.gradle
@@ -35,7 +35,7 @@
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.testRules)
     androidTestImplementation(libs.espressoCore, excludes.espresso)
-    androidTestImplementation(libs.mockitoCore, excludes.bytebuddy)
+    androidTestImplementation(libs.mockitoCore4, excludes.bytebuddy)
     androidTestImplementation(libs.dexmakerMockito, excludes.bytebuddy)
 }
 
diff --git a/slice/slice-view/src/androidTest/java/androidx/slice/SliceViewManagerTest.java b/slice/slice-view/src/androidTest/java/androidx/slice/SliceViewManagerTest.java
index 38784fa..1c6b91b 100644
--- a/slice/slice-view/src/androidTest/java/androidx/slice/SliceViewManagerTest.java
+++ b/slice/slice-view/src/androidTest/java/androidx/slice/SliceViewManagerTest.java
@@ -42,7 +42,6 @@
 import androidx.slice.widget.SliceLiveData;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.SdkSuppress;
 
@@ -110,7 +109,6 @@
         verify(mSliceProvider, timeout(2000)).onSliceUnpinned(eq(uri));
     }
 
-    @FlakyTest(bugId = 239964752)
     @Test
     @SdkSuppress(maxSdkVersion = 33) // b/262909049: Failing on SDK 34
     public void testPinList() {
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/MultiWindowTest.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/MultiWindowTest.java
index fdfd994..016feb1 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/MultiWindowTest.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/MultiWindowTest.java
@@ -78,6 +78,7 @@
         }
     }
 
+    @Ignore // b/264940470
     @Test
     @SdkSuppress(minSdkVersion = 24)
     public void testMultiWindow_pictureInPicture() {
diff --git a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt b/testutils/testutils-kmp/src/commonMain/kotlin/androidx/kruth/FailingOrdered.kt
similarity index 64%
copy from tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
copy to testutils/testutils-kmp/src/commonMain/kotlin/androidx/kruth/FailingOrdered.kt
index ebac64e..559e0e6 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
+++ b/testutils/testutils-kmp/src/commonMain/kotlin/androidx/kruth/FailingOrdered.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * 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.
@@ -14,10 +14,18 @@
  * limitations under the License.
  */
 
-package androidx.tv.material
+package androidx.kruth
 
-@RequiresOptIn(
-    "This tv-material API is experimental and likely to change or be removed in the future."
-)
-@Retention(AnnotationRetention.BINARY)
-annotation class ExperimentalTvMaterialApi
\ No newline at end of file
+import kotlin.test.fail
+
+/**
+ * Always fails with the provided error message.
+ */
+internal class FailingOrdered(
+    private val message: () -> String,
+) : Ordered {
+
+    override fun inOrder() {
+        fail(message())
+    }
+}
\ No newline at end of file
diff --git a/testutils/testutils-kmp/src/commonMain/kotlin/androidx/kruth/IterableSubject.kt b/testutils/testutils-kmp/src/commonMain/kotlin/androidx/kruth/IterableSubject.kt
index f366d55..2bad306 100644
--- a/testutils/testutils-kmp/src/commonMain/kotlin/androidx/kruth/IterableSubject.kt
+++ b/testutils/testutils-kmp/src/commonMain/kotlin/androidx/kruth/IterableSubject.kt
@@ -151,6 +151,84 @@
         containsAnyIn(requireNonNull(expected).asList())
     }
 
+    fun containsAtLeast(
+        firstExpected: Any?,
+        secondExpected: Any?,
+        vararg restOfExpected: Any?,
+    ): Ordered =
+        containsAtLeastElementsIn(listOf(firstExpected, secondExpected, *restOfExpected))
+
+    fun containsAtLeastElementsIn(expected: Iterable<*>?): Ordered {
+        requireNonNull(expected)
+        val actualList = requireNonNull(actual).toMutableList()
+
+        val missing = ArrayList<Any?>()
+        val actualNotInOrder = ArrayList<Any?>()
+
+        var ordered = true
+        // step through the expected elements...
+        for (e in expected) {
+            val index = actualList.indexOf(e)
+            if (index != -1) { // if we find the element in the actual list...
+                // drain all the elements that come before that element into actualNotInOrder
+                repeat(index) {
+                    actualNotInOrder += actualList.removeAt(0)
+                }
+
+                // and remove the element from the actual list
+                actualList.removeAt(0)
+            } else { // otherwise try removing it from actualNotInOrder...
+                if (actualNotInOrder.remove(e)) {
+                    // if it was in actualNotInOrder, we're not in order
+                    ordered = false
+                } else {
+                    // if it's not in actualNotInOrder, we're missing an expected element
+                    missing.add(e)
+                }
+            }
+        }
+
+        // if we have any missing expected elements, fail
+        if (missing.isNotEmpty()) {
+            val nearMissing = actualList.retainMatchingToString(missing)
+
+            fail(
+                """
+                    Expected to contain at least $expected, but did not.
+                    Missing $missing, though it did contain $nearMissing.
+                """.trimIndent()
+            )
+        }
+
+        if (ordered) {
+            return NoopOrdered
+        }
+
+        return FailingOrdered {
+            buildString {
+                append("Required elements were all found, but order was wrong.")
+                append("Expected order: $expected.")
+
+                if (actualList.any { it !in expected }) {
+                    append("Actual order: $actualList.")
+                }
+            }
+        }
+    }
+
+    /**
+     * Checks that the actual iterable contains at least all of the expected elements or fails. If
+     * an element appears more than once in the expected elements then it must appear at least that
+     * number of times in the actual elements.
+     *
+     *
+     * To also test that the contents appear in the given order, make a call to `inOrder()`
+     * on the object returned by this method. The expected elements must appear in the given order
+     * within the actual elements, but they are not required to be consecutive.
+     */
+    fun containsAtLeastElementsIn(expected: Array<Any?>?): Ordered =
+        containsAtLeastElementsIn(expected?.asList())
+
     /**
      * Checks that the subject contains exactly the provided objects or fails.
      *
@@ -233,16 +311,13 @@
                      * This containsExactly() call is a success. But the iterables were not in the same order,
                      * so return an object that will fail the test if the user calls inOrder().
                      */
-                    return object : Ordered {
-                        override fun inOrder() {
-                            fail(
-                                """
-                                    Contents match. Expected the order to also match, but was not.
-                                    Expected: $required.
-                                    Actual: $actual.
-                                """.trimIndent()
-                            )
-                        }
+
+                    return FailingOrdered {
+                        """
+                             Contents match. Expected the order to also match, but was not.
+                             Expected: $required.
+                             Actual: $actual.
+                        """.trimIndent()
                     }
                 }
 
diff --git a/testutils/testutils-kmp/src/commonTest/kotlin/androidx/kruth/IterableSubjectTest.kt b/testutils/testutils-kmp/src/commonTest/kotlin/androidx/kruth/IterableSubjectTest.kt
index 60ecd75..ba97a52 100644
--- a/testutils/testutils-kmp/src/commonTest/kotlin/androidx/kruth/IterableSubjectTest.kt
+++ b/testutils/testutils-kmp/src/commonTest/kotlin/androidx/kruth/IterableSubjectTest.kt
@@ -192,259 +192,232 @@
         }
     }
 
-//    @Test
-//    fun iterableContainsAtLeast() {
-//        assertThat(listOf(1, 2, 3)).containsAtLeast(1, 2)
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastWithMany() {
-//        assertThat(listOf(1, 2, 3)).containsAtLeast(1, 2)
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastWithDuplicates() {
-//        assertThat(listOf(1, 2, 2, 2, 3)).containsAtLeast(2, 2)
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastWithNull() {
-//        assertThat(listOf(1, null, 3)).containsAtLeast(3, null as Int?)
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastWithNullAtThirdAndFinalPosition() {
-//        assertThat(listOf(1, null, 3)).containsAtLeast(1, 3, null as Any?)
-//    }
-//
-//    /*
-//   * Test that we only call toString() if the assertion fails -- that is, not just if the elements
-//   * are out of order, but only if someone actually calls inOrder(). There are 2 reasons for this:
-//   *
-//   * 1. Calling toString() uses extra time and space. (To be fair, Iterable assertions often use a
-//   * lot of those already.)
-//   *
-//   * 2. Some toString() methods are buggy. Arguably we shouldn't accommodate these, especially since
-//   * those users are in for a nasty surprise if their tests actually fail someday, but I don't want
-//   * to bite that off now. (Maybe Fact should catch exceptions from toString()?)
-//   */
-//    @Test
-//    fun iterableContainsAtLeastElementsInOutOfOrderDoesNotStringify() {
-//        val o = CountsToStringCalls()
-//        val actual: List<Any> = listOf(o, 1)
-//        val expected: List<Any> = listOf(1, o)
-//        assertThat(actual).containsAtLeastElementsIn(expected)
-//        assertThat(o.calls).isEqualTo(0)
-//        expectFailureWhenTestingThat(actual).containsAtLeastElementsIn(expected).inOrder()
-//        assertThat(o.calls).isGreaterThan(0)
-//    }
+    @Test
+    fun iterableContainsAtLeast() {
+        assertThat(listOf(1, 2, 3)).containsAtLeast(1, 2)
+    }
 
-//    @Test
-//    fun iterableContainsAtLeastFailure() {
-//        expectFailureWhenTestingThat(listOf(1, 2, 3)).containsAtLeast(1, 2, 4)
-//        assertFailureKeys("missing (1)", "---", "expected to contain at least", "but was")
-//        assertFailureValue("missing (1)", "4")
-//        assertFailureValue("expected to contain at least", "[1, 2, 4]")
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastWithExtras() {
-//        expectFailureWhenTestingThat(listOf("y", "x")).containsAtLeast("x", "y", "z")
-//        assertFailureValue("missing (1)", "z")
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastWithExtraCopiesOfOutOfOrder() {
-//        expectFailureWhenTestingThat(listOf("y", "x")).containsAtLeast("x", "y", "y")
-//        assertFailureValue("missing (1)", "y")
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastWithDuplicatesFailure() {
-//        expectFailureWhenTestingThat(listOf(1, 2, 3)).containsAtLeast(1, 2, 2, 2, 3, 4)
-//        assertFailureValue("missing (3)", "2 [2 copies], 4")
-//    }
-//
-//    /*
-//   * Slightly subtle test to ensure that if multiple equal elements are found
-//   * to be missing we only reference it once in the output message.
-//   */
-//    @Test
-//    fun iterableContainsAtLeastWithDuplicateMissingElements() {
-//        expectFailureWhenTestingThat(listOf(1, 2)).containsAtLeast(4, 4, 4)
-//        assertFailureValue("missing (3)", "4 [3 copies]")
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastWithNullFailure() {
-//        expectFailureWhenTestingThat(listOf(1, null, 3)).containsAtLeast(1, null, null, 3)
-//        assertFailureValue("missing (1)", "null")
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastFailsWithSameToStringAndHomogeneousList() {
-//        expectFailureWhenTestingThat(listOf(1L, 2L)).containsAtLeast(1, 2)
-//        assertFailureValue("missing (2)", "1, 2 (java.lang.Integer)")
-//        assertFailureValue("though it did contain (2)", "1, 2 (java.lang.Long)")
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastFailsWithSameToStringAndHomogeneousListWithDuplicates() {
-//        expectFailureWhenTestingThat(listOf(1L, 2L, 2L)).containsAtLeast(1, 1, 2)
-//        assertFailureValue("missing (3)", "1 [2 copies], 2 (java.lang.Integer)")
-//        assertFailureValue("though it did contain (3)", "1, 2 [2 copies] (java.lang.Long)")
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastFailsWithSameToStringAndHomogeneousListWithNull() {
-//        expectFailureWhenTestingThat(listOf("null", "abc")).containsAtLeast("abc", null)
-//        assertFailureValue("missing (1)", "null (null type)")
-//        assertFailureValue("though it did contain (1)", "null (java.lang.String)")
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastFailsWithSameToStringAndHeterogeneousListWithDuplicates() {
-//        expectFailureWhenTestingThat(listOf(1, 2, 2L, 3L, 3L)).containsAtLeast(2L, 2L, 3, 3)
-//        assertFailureValue("missing (3)", "2 (java.lang.Long), 3 (java.lang.Integer) [2 copies]")
-//        assertFailureValue(
-//            "though it did contain (3)", "2 (java.lang.Integer), 3 (java.lang.Long) [2 copies]"
-//        )
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastFailsWithEmptyString() {
-//        expectFailureWhenTestingThat(listOf("a", null)).containsAtLeast("", null)
-//        assertFailureKeys("missing (1)", "---", "expected to contain at least", "but was")
-//        assertFailureValue("missing (1)", "")
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastInOrder() {
-//        assertThat(listOf(3, 2, 5)).containsAtLeast(3, 2, 5).inOrder()
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastInOrderWithGaps() {
-//        assertThat(listOf(3, 2, 5)).containsAtLeast(3, 5).inOrder()
-//        assertThat(listOf(3, 2, 2, 4, 5)).containsAtLeast(3, 2, 2, 5).inOrder()
-//        assertThat(listOf(3, 1, 4, 1, 5)).containsAtLeast(3, 1, 5).inOrder()
-//        assertThat(listOf("x", "y", "y", "z")).containsAtLeast("x", "y", "z").inOrder()
-//        assertThat(listOf("x", "x", "y", "z")).containsAtLeast("x", "y", "z").inOrder()
-//        assertThat(listOf("z", "x", "y", "z")).containsAtLeast("x", "y", "z").inOrder()
-//        assertThat(listOf("x", "x", "y", "z", "x")).containsAtLeast("x", "y", "z", "x").inOrder()
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastInOrderWithNull() {
-//        assertThat(listOf(3, null, 5)).containsAtLeast(3, null, 5).inOrder()
-//        assertThat(listOf(3, null, 7, 5)).containsAtLeast(3, null, 5).inOrder()
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastInOrderWithFailure() {
-//        expectFailureWhenTestingThat(listOf(1, null, 3)).containsAtLeast(null, 1, 3).inOrder()
-//        assertFailureKeys(
-//            "required elements were all found, but order was wrong",
-//            "expected order for required elements",
-//            "but was"
-//        )
-//        assertFailureValue("expected order for required elements", "[null, 1, 3]")
-//        assertFailureValue("but was", "[1, null, 3]")
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastInOrderWithFailureWithActualOrder() {
-//        expectFailureWhenTestingThat(listOf(1, 2, null, 3, 4)).containsAtLeast(null, 1, 3).inOrder()
-//        assertFailureKeys(
-//            "required elements were all found, but order was wrong",
-//            "expected order for required elements",
-//            "but order was",
-//            "full contents"
-//        )
-//        assertFailureValue("expected order for required elements", "[null, 1, 3]")
-//        assertFailureValue("but order was", "[1, null, 3]")
-//        assertFailureValue("full contents", "[1, 2, null, 3, 4]")
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastInOrderWithOneShotIterable() {
-//        val iterable: Iterable<Any> =
-//            Arrays.< Object > asList < kotlin . Any ? > 2, 1, null, 4, "a", 3, "b")
-//        val iterator = iterable.iterator()
-//        val oneShot: Iterable<Any> = object : Iterable<Any?> {
-//            override fun iterator(): Iterator<Any> {
-//                return iterator
-//            }
-//
-//            override fun toString(): String {
-//                return Iterables.toString(iterable)
-//            }
-//        }
-//        assertThat(oneShot).containsAtLeast(1, null, 3).inOrder()
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastInOrderWithOneShotIterableWrongOrder() {
-//        val iterator: Iterator<Any> = listOf(2 as Any, 1, null, 4, "a", 3, "b").iterator()
-//        val iterable: Iterable<Any> = object : Iterable<Any?> {
-//            override fun iterator(): Iterator<Any> {
-//                return iterator
-//            }
-//
-//            override fun toString(): String {
-//                return "BadIterable"
-//            }
-//        }
-//        expectFailureWhenTestingThat(iterable).containsAtLeast(1, 3, null as Any?).inOrder()
-//        assertFailureKeys(
-//            "required elements were all found, but order was wrong",
-//            "expected order for required elements",
-//            "but was"
-//        )
-//        assertFailureValue("expected order for required elements", "[1, 3, null]")
-//        assertFailureValue("but was", "BadIterable") // TODO(b/231966021): Output its elements.
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastInOrderWrongOrderAndMissing() {
-//        expectFailureWhenTestingThat(listOf(1, 2)).containsAtLeast(2, 1, 3).inOrder()
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastElementsInIterable() {
-//        assertThat(listOf(1, 2, 3)).containsAtLeastElementsIn(listOf(1, 2))
-//        expectFailureWhenTestingThat(listOf(1, 2, 3)).containsAtLeastElementsIn(listOf(1, 2, 4))
-//        assertFailureKeys("missing (1)", "---", "expected to contain at least", "but was")
-//        assertFailureValue("missing (1)", "4")
-//        assertFailureValue("expected to contain at least", "[1, 2, 4]")
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastElementsInCanUseFactPerElement() {
-//        expectFailureWhenTestingThat(listOf("abc"))
-//            .containsAtLeastElementsIn(listOf("123\n456", "789"))
-//        assertFailureKeys(
-//            "missing (2)",
-//            "#1",
-//            "#2",
-//            "---",
-//            "expected to contain at least",
-//            "but was"
-//        )
-//        assertFailureValue("#1", "123\n456")
-//        assertFailureValue("#2", "789")
-//    }
-//
-//    @Test
-//    fun iterableContainsAtLeastElementsInArray() {
-//        assertThat(listOf(1, 2, 3)).containsAtLeastElementsIn(arrayOf(1, 2))
-//        expectFailureWhenTestingThat(listOf(1, 2, 3))
-//            .containsAtLeastElementsIn(arrayOf(1, 2, 4))
-//        assertFailureKeys("missing (1)", "---", "expected to contain at least", "but was")
-//        assertFailureValue("missing (1)", "4")
-//        assertFailureValue("expected to contain at least", "[1, 2, 4]")
-//    }
-//
+    @Test
+    fun iterableContainsAtLeastWithMany() {
+        assertThat(listOf(1, 2, 3)).containsAtLeast(1, 2)
+    }
+
+    @Test
+    fun iterableContainsAtLeastWithDuplicates() {
+        assertThat(listOf(1, 2, 2, 2, 3)).containsAtLeast(2, 2)
+    }
+
+    @Test
+    fun iterableContainsAtLeastWithNull() {
+        assertThat(listOf(1, null, 3)).containsAtLeast(3, null as Int?)
+    }
+
+    @Test
+    fun iterableContainsAtLeastWithNullAtThirdAndFinalPosition() {
+        assertThat(listOf(1, null, 3)).containsAtLeast(1, 3, null as Any?)
+    }
+
+    /*
+     * Test that we only call toString() if the assertion fails -- that is, not just if the elements
+     * are out of order, but only if someone actually calls inOrder(). There are 2 reasons for this:
+     *
+     * 1. Calling toString() uses extra time and space. (To be fair, Iterable assertions often use a
+     * lot of those already.)
+     *
+     * 2. Some toString() methods are buggy. Arguably we shouldn't accommodate these, especially since
+     * those users are in for a nasty surprise if their tests actually fail someday, but I don't want
+     * to bite that off now. (Maybe Fact should catch exceptions from toString()?)
+     */
+    @Test
+    fun iterableContainsAtLeastElementsInOutOfOrderDoesNotStringify() {
+        val o = CountsToStringCalls()
+        val actual: List<Any> = listOf(o, 1)
+        val expected: List<Any> = listOf(1, o)
+        assertThat(actual).containsAtLeastElementsIn(expected)
+        assertThat(o.calls).isEqualTo(0)
+
+        assertFailsWith<AssertionError> {
+            assertThat(actual).containsAtLeastElementsIn(expected).inOrder()
+        }
+
+        assertThat(o.calls > 0)
+    }
+
+    @Test
+    fun iterableContainsAtLeastFailure() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, 3)).containsAtLeast(1, 2, 4)
+        }
+    }
+
+    @Test
+    fun iterableContainsAtLeastWithExtras() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf("y", "x")).containsAtLeast("x", "y", "z")
+        }
+    }
+
+    @Test
+    fun iterableContainsAtLeastWithExtraCopiesOfOutOfOrder() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf("y", "x")).containsAtLeast("x", "y", "y")
+        }
+    }
+
+    @Test
+    fun iterableContainsAtLeastWithDuplicatesFailure() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, 3)).containsAtLeast(1, 2, 2, 2, 3, 4)
+        }
+    }
+
+    /*
+     * Slightly subtle test to ensure that if multiple equal elements are found
+     * to be missing we only reference it once in the output message.
+     */
+    @Test
+    fun iterableContainsAtLeastWithDuplicateMissingElements() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2)).containsAtLeast(4, 4, 4)
+        }
+    }
+
+    @Test
+    fun iterableContainsAtLeastWithNullFailure() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, null, 3)).containsAtLeast(1, null, null, 3)
+        }
+    }
+
+    @Test
+    fun iterableContainsAtLeastFailsWithSameToStringAndHomogeneousList() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1L, 2L)).containsAtLeast(1, 2)
+        }
+    }
+
+    @Test
+    fun iterableContainsAtLeastFailsWithSameToStringAndHomogeneousListWithDuplicates() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1L, 2L, 2L)).containsAtLeast(1, 1, 2)
+        }
+    }
+
+    @Test
+    fun iterableContainsAtLeastFailsWithSameToStringAndHomogeneousListWithNull() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf("null", "abc")).containsAtLeast("abc", null)
+        }
+    }
+
+    @Test
+    fun iterableContainsAtLeastFailsWithSameToStringAndHeterogeneousListWithDuplicates() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, 2L, 3L, 3L)).containsAtLeast(2L, 2L, 3, 3)
+        }
+    }
+
+    @Test
+    fun iterableContainsAtLeastFailsWithEmptyString() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf("a", null)).containsAtLeast("", null)
+        }
+    }
+
+    @Test
+    fun iterableContainsAtLeastInOrder() {
+        assertThat(listOf(3, 2, 5)).containsAtLeast(3, 2, 5).inOrder()
+    }
+
+    @Test
+    fun iterableContainsAtLeastInOrderWithGaps() {
+        assertThat(listOf(3, 2, 5)).containsAtLeast(3, 5).inOrder()
+        assertThat(listOf(3, 2, 2, 4, 5)).containsAtLeast(3, 2, 2, 5).inOrder()
+        assertThat(listOf(3, 1, 4, 1, 5)).containsAtLeast(3, 1, 5).inOrder()
+        assertThat(listOf("x", "y", "y", "z")).containsAtLeast("x", "y", "z").inOrder()
+        assertThat(listOf("x", "x", "y", "z")).containsAtLeast("x", "y", "z").inOrder()
+        assertThat(listOf("z", "x", "y", "z")).containsAtLeast("x", "y", "z").inOrder()
+        assertThat(listOf("x", "x", "y", "z", "x")).containsAtLeast("x", "y", "z", "x").inOrder()
+    }
+
+    @Test
+    fun iterableContainsAtLeastInOrderWithNull() {
+        assertThat(listOf(3, null, 5)).containsAtLeast(3, null, 5).inOrder()
+        assertThat(listOf(3, null, 7, 5)).containsAtLeast(3, null, 5).inOrder()
+    }
+
+    @Test
+    fun iterableContainsAtLeastInOrderWithFailure() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, null, 3)).containsAtLeast(null, 1, 3).inOrder()
+        }
+    }
+
+    @Test
+    fun iterableContainsAtLeastInOrderWithFailureWithActualOrder() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, null, 3, 4)).containsAtLeast(null, 1, 3).inOrder()
+        }
+    }
+
+    @Test
+    fun iterableContainsAtLeastInOrderWithOneShotIterable() {
+        val iterable = listOf(2, 1, null, 4, "a", 3, "b")
+        val iterator = iterable.iterator()
+
+        val oneShot =
+            object : Iterable<Any?> {
+                override fun iterator(): Iterator<Any?> = iterator
+            }
+
+        assertThat(oneShot).containsAtLeast(1, null, 3).inOrder()
+    }
+
+    @Test
+    fun iterableContainsAtLeastInOrderWithOneShotIterableWrongOrder() {
+        val iterator = listOf(2, 1, null, 4, "a", 3, "b").iterator()
+
+        val iterable =
+            object : Iterable<Any?> {
+                override fun iterator(): Iterator<Any?> = iterator
+            }
+        assertFailsWith<AssertionError> {
+            assertThat(iterable).containsAtLeast(1, 3, null as Any?).inOrder()
+        }
+    }
+
+    @Test
+    fun iterableContainsAtLeastInOrderWrongOrderAndMissing() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2)).containsAtLeast(2, 1, 3).inOrder()
+        }
+    }
+
+    @Test
+    fun iterableContainsAtLeastElementsInIterable() {
+        assertThat(listOf(1, 2, 3)).containsAtLeastElementsIn(listOf(1, 2))
+
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, 3)).containsAtLeastElementsIn(listOf(1, 2, 4))
+        }
+    }
+
+    @Test
+    fun iterableContainsAtLeastElementsInCanUseFactPerElement() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf("abc")).containsAtLeastElementsIn(listOf("123\n456", "789"))
+        }
+    }
+
+    @Test
+    fun iterableContainsAtLeastElementsInArray() {
+        assertThat(listOf(1, 2, 3)).containsAtLeastElementsIn(arrayOf(1, 2))
+
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, 3)).containsAtLeastElementsIn(arrayOf(1, 2, 4))
+        }
+    }
+
 //    @Test
 //    fun iterableContainsNoneOf() {
 //        assertThat(listOf(1, 2, 3)).containsNoneOf(4, 5, 6)
diff --git a/tracing/tracing-perfetto-binary/src/main/cpp/tracing_perfetto.cc b/tracing/tracing-perfetto-binary/src/main/cpp/tracing_perfetto.cc
index 68efd0c..72776fc 100644
--- a/tracing/tracing-perfetto-binary/src/main/cpp/tracing_perfetto.cc
+++ b/tracing/tracing-perfetto-binary/src/main/cpp/tracing_perfetto.cc
@@ -25,7 +25,7 @@
 // Concept of version useful e.g. for human-readable error messages, and stable once released.
 // Does not replace the need for a binary verification mechanism (e.g. checksum check).
 // TODO: populate using CMake
-#define VERSION "1.0.0-alpha09"
+#define VERSION "1.0.0-alpha10"
 
 namespace tracing_perfetto {
     void RegisterWithPerfetto() {
diff --git a/tracing/tracing-perfetto/src/androidTest/java/androidx/tracing/perfetto/jni/test/PerfettoNativeTest.kt b/tracing/tracing-perfetto/src/androidTest/java/androidx/tracing/perfetto/jni/test/PerfettoNativeTest.kt
index 66a4743..7178a50 100644
--- a/tracing/tracing-perfetto/src/androidTest/java/androidx/tracing/perfetto/jni/test/PerfettoNativeTest.kt
+++ b/tracing/tracing-perfetto/src/androidTest/java/androidx/tracing/perfetto/jni/test/PerfettoNativeTest.kt
@@ -30,7 +30,7 @@
         init {
             PerfettoNative.loadLib()
         }
-        const val libraryVersion = "1.0.0-alpha09" // TODO: get using reflection
+        const val libraryVersion = "1.0.0-alpha10" // TODO: get using reflection
     }
 
     @Test
diff --git a/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt b/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt
index efa3607..5278ec7 100644
--- a/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt
+++ b/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt
@@ -25,12 +25,12 @@
 
     // TODO(224510255): load from a file produced at build time
     object Metadata {
-        const val version = "1.0.0-alpha09"
+        const val version = "1.0.0-alpha10"
         val checksums = mapOf(
-            "arm64-v8a" to "130cea2d90e941acb9d2087c0bebe8229b461175454df20763bfe901c0d73155",
-            "armeabi-v7a" to "cb0e60c5d8726d274bbc678cfa3a8724e66d2bfa6874c06c5f4347e545c0439d",
-            "x86" to "c6b19ce773d17c74aa74aa813637bc1d2bd61f5bcc280e6a5c030a36948dd6dd",
-            "x86_64" to "af62d109588db30c8ce11f2027227664520372f8c77259d4155dd92f7cc73a6e",
+            "arm64-v8a" to "189348f277f588ff41f51b4284bf0568bf3d0eab786f6664f8d66847694e0674",
+            "armeabi-v7a" to "9550b32340d790d58fe069b3882815e3d2b20474aa634f01e448392e898cc95b",
+            "x86" to "07967aaad711d16f35f509783f56139903f28f661ebb0415522c10fc0453810f",
+            "x86_64" to "6b2f4129cb1e52d9c9dca5b89d575c7b2a905670da2e0474917d37223f0c8bf5",
         )
     }
 
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/App.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/App.kt
index 7898201..00ea81e 100644
--- a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/App.kt
+++ b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/App.kt
@@ -26,16 +26,24 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
+import androidx.tv.material3.ExperimentalTvMaterial3Api
+import androidx.tv.material3.MaterialTheme
+import androidx.tv.material3.darkColorScheme
 
+@OptIn(ExperimentalTvMaterial3Api::class)
 @Composable
 fun App() {
     var selectedTab by remember { mutableStateOf(Navigation.FeaturedCarousel) }
 
-    Column(
-        modifier = Modifier.padding(20.dp),
-        verticalArrangement = Arrangement.spacedBy(20.dp),
+    MaterialTheme(
+        colorScheme = darkColorScheme()
     ) {
-        TopNavigation(updateSelectedTab = { selectedTab = it })
-        selectedTab.action.invoke()
+        Column(
+            modifier = Modifier.padding(20.dp),
+            verticalArrangement = Arrangement.spacedBy(20.dp)
+        ) {
+            TopNavigation(updateSelectedTab = { selectedTab = it })
+            selectedTab.action.invoke()
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/FeaturedCarousel.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/FeaturedCarousel.kt
index 5cecda2..821281f 100644
--- a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/FeaturedCarousel.kt
+++ b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/FeaturedCarousel.kt
@@ -42,11 +42,11 @@
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
-import androidx.tv.material.ExperimentalTvMaterialApi
-import androidx.tv.material.carousel.Carousel
-import androidx.tv.material.carousel.CarouselDefaults
-import androidx.tv.material.carousel.CarouselItem
-import androidx.tv.material.carousel.CarouselState
+import androidx.tv.material3.ExperimentalTvMaterial3Api
+import androidx.tv.material3.Carousel
+import androidx.tv.material3.CarouselDefaults
+import androidx.tv.material3.CarouselItem
+import androidx.tv.material3.CarouselState
 
 @Composable
 fun FeaturedCarouselContent() {
@@ -81,7 +81,7 @@
         .onFocusChanged { isFocused = it.isFocused }
 }
 
-@OptIn(ExperimentalTvMaterialApi::class)
+@OptIn(ExperimentalTvMaterial3Api::class)
 @Composable
 internal fun FeaturedCarousel() {
     val backgrounds = listOf(
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/ImmersiveList.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/ImmersiveList.kt
index a3bcc25..a690908 100644
--- a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/ImmersiveList.kt
+++ b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/ImmersiveList.kt
@@ -16,8 +16,10 @@
 
 package androidx.tv.integration.demos
 
+import android.util.Log
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Row
@@ -34,8 +36,8 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
 import androidx.tv.foundation.lazy.list.TvLazyColumn
-import androidx.tv.material.ExperimentalTvMaterialApi
-import androidx.tv.material.immersivelist.ImmersiveList
+import androidx.tv.material3.ExperimentalTvMaterial3Api
+import androidx.tv.material3.ImmersiveList
 
 @Composable
 fun ImmersiveListContent() {
@@ -46,7 +48,7 @@
     }
 }
 
-@OptIn(ExperimentalTvMaterialApi::class)
+@OptIn(ExperimentalTvMaterial3Api::class)
 @Composable
 private fun SampleImmersiveList() {
     val immersiveListHeight = 300.dp
@@ -60,7 +62,9 @@
     )
 
     ImmersiveList(
-        modifier = Modifier.height(immersiveListHeight + cardHeight / 2).fillMaxWidth(),
+        modifier = Modifier
+            .height(immersiveListHeight + cardHeight / 2)
+            .fillMaxWidth(),
         background = { index, _ ->
             Box(
                 modifier = Modifier
@@ -81,7 +85,10 @@
                         .height(cardHeight)
                         .border(5.dp, Color.White.copy(alpha = if (isFocused) 1f else 0.3f))
                         .onFocusChanged { isFocused = it.isFocused }
-                        .focusableItem(index)
+                        .immersiveListItem(index)
+                        .clickable {
+                            Log.d("ImmersiveList", "Item $index was clicked")
+                        }
                 )
             }
         }
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/TopNavigation.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/TopNavigation.kt
index a7c2da4..1b34fd8 100644
--- a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/TopNavigation.kt
+++ b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/TopNavigation.kt
@@ -29,9 +29,9 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
-import androidx.tv.material.LocalContentColor
-import androidx.tv.material.Tab
-import androidx.tv.material.TabRow
+import androidx.tv.material3.LocalContentColor
+import androidx.tv.material3.Tab
+import androidx.tv.material3.TabRow
 import kotlinx.coroutines.delay
 
 enum class Navigation(val displayName: String, val action: @Composable () -> Unit) {
diff --git a/tv/samples/src/main/java/androidx/tv/samples/CarouselSamples.kt b/tv/samples/src/main/java/androidx/tv/samples/CarouselSamples.kt
index 3916c9f..b235403 100644
--- a/tv/samples/src/main/java/androidx/tv/samples/CarouselSamples.kt
+++ b/tv/samples/src/main/java/androidx/tv/samples/CarouselSamples.kt
@@ -39,13 +39,13 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.unit.dp
-import androidx.tv.material.ExperimentalTvMaterialApi
-import androidx.tv.material.carousel.Carousel
-import androidx.tv.material.carousel.CarouselDefaults
-import androidx.tv.material.carousel.CarouselItem
-import androidx.tv.material.carousel.CarouselState
+import androidx.tv.material3.ExperimentalTvMaterial3Api
+import androidx.tv.material3.Carousel
+import androidx.tv.material3.CarouselDefaults
+import androidx.tv.material3.CarouselItem
+import androidx.tv.material3.CarouselState
 
-@OptIn(ExperimentalTvMaterialApi::class)
+@OptIn(ExperimentalTvMaterial3Api::class)
 @Sampled
 @Composable
 fun SimpleCarousel() {
@@ -92,7 +92,7 @@
     }
 }
 
-@OptIn(ExperimentalTvMaterialApi::class)
+@OptIn(ExperimentalTvMaterial3Api::class)
 @Sampled
 @Composable
 fun CarouselIndicatorWithRectangleShape() {
diff --git a/tv/samples/src/main/java/androidx/tv/samples/ImmersiveListSamples.kt b/tv/samples/src/main/java/androidx/tv/samples/ImmersiveListSamples.kt
new file mode 100644
index 0000000..9654fd7
--- /dev/null
+++ b/tv/samples/src/main/java/androidx/tv/samples/ImmersiveListSamples.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.tv.samples
+
+import android.util.Log
+import androidx.annotation.Sampled
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import androidx.tv.material3.ExperimentalTvMaterial3Api
+import androidx.tv.material3.ImmersiveList
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Sampled
+@Composable
+private fun SampleImmersiveList() {
+    val immersiveListHeight = 300.dp
+    val cardSpacing = 10.dp
+    val cardWidth = 200.dp
+    val cardHeight = 150.dp
+    val backgrounds = listOf(
+        Color.Red,
+        Color.Blue,
+        Color.Magenta,
+    )
+
+    ImmersiveList(
+        modifier = Modifier
+            .height(immersiveListHeight + cardHeight / 2)
+            .fillMaxWidth(),
+        background = { index, _ ->
+            Box(
+                modifier = Modifier
+                    .background(backgrounds[index].copy(alpha = 0.3f))
+                    .height(immersiveListHeight)
+                    .fillMaxWidth()
+            )
+        }
+    ) {
+        Row(horizontalArrangement = Arrangement.spacedBy(cardSpacing)) {
+            backgrounds.forEachIndexed { index, backgroundColor ->
+                var isFocused by remember { mutableStateOf(false) }
+
+                Box(
+                    modifier = Modifier
+                        .background(backgroundColor)
+                        .width(cardWidth)
+                        .height(cardHeight)
+                        .border(5.dp, Color.White.copy(alpha = if (isFocused) 1f else 0.3f))
+                        .onFocusChanged { isFocused = it.isFocused }
+                        .immersiveListItem(index)
+                        .clickable {
+                            Log.d("ImmersiveList", "Item $index was clicked")
+                        }
+                )
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tv/samples/src/main/java/androidx/tv/samples/TabRowSamples.kt b/tv/samples/src/main/java/androidx/tv/samples/TabRowSamples.kt
index c8d85ab..b2c506e 100644
--- a/tv/samples/src/main/java/androidx/tv/samples/TabRowSamples.kt
+++ b/tv/samples/src/main/java/androidx/tv/samples/TabRowSamples.kt
@@ -34,11 +34,11 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
-import androidx.tv.material.LocalContentColor
-import androidx.tv.material.Tab
-import androidx.tv.material.TabDefaults
-import androidx.tv.material.TabRow
-import androidx.tv.material.TabRowDefaults
+import androidx.tv.material3.LocalContentColor
+import androidx.tv.material3.Tab
+import androidx.tv.material3.TabDefaults
+import androidx.tv.material3.TabRow
+import androidx.tv.material3.TabRowDefaults
 import kotlin.time.Duration.Companion.microseconds
 import kotlinx.coroutines.delay
 
diff --git a/tv/tv-foundation/src/main/java/androidx/tv/foundation/ScrollableWithPivot.kt b/tv/tv-foundation/src/main/java/androidx/tv/foundation/ScrollableWithPivot.kt
index c21cc0c..619f0a2 100644
--- a/tv/tv-foundation/src/main/java/androidx/tv/foundation/ScrollableWithPivot.kt
+++ b/tv/tv-foundation/src/main/java/androidx/tv/foundation/ScrollableWithPivot.kt
@@ -20,7 +20,6 @@
 import androidx.compose.foundation.focusGroup
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.Orientation.Horizontal
-import androidx.compose.foundation.gestures.Orientation.Vertical
 import androidx.compose.foundation.gestures.ScrollableState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
@@ -31,12 +30,10 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.ScrollContainerInfo
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.input.nestedscroll.nestedScroll
-import androidx.compose.ui.input.provideScrollContainerInfo
 import androidx.compose.ui.platform.debugInspectorInfo
 
 /* Copied from
@@ -118,17 +115,7 @@
         scrollableNestedScrollConnection(scrollLogic, enabled)
     }
 
-    val scrollContainerInfo = remember(orientation, enabled) {
-        object : ScrollContainerInfo {
-            override fun canScrollHorizontally() = enabled && orientation == Horizontal
-
-            override fun canScrollVertically() = enabled && orientation == Vertical
-        }
-    }
-
-    return this
-        .nestedScroll(nestedScrollConnection, nestedScrollDispatcher.value)
-        .provideScrollContainerInfo(scrollContainerInfo)
+    return this.nestedScroll(nestedScrollConnection, nestedScrollDispatcher.value)
 }
 
 private class ScrollingLogic(
diff --git a/tv/tv-material/api/current.txt b/tv/tv-material/api/current.txt
index 0c788a4..0b46921 100644
--- a/tv/tv-material/api/current.txt
+++ b/tv/tv-material/api/current.txt
@@ -1,25 +1,81 @@
 // Signature format: 4.0
-package androidx.tv.material {
+package androidx.tv.material3 {
 
   public final class BringIntoViewIfChildrenAreFocusedKt {
   }
 
+  public final class CarouselItemKt {
+  }
+
+  public final class CarouselKt {
+  }
+
+  public final class ColorSchemeKt {
+  }
+
   public final class ContentColorKt {
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> getLocalContentColor();
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> LocalContentColor;
   }
 
+  public final class ImmersiveListKt {
+  }
+
+  public final class MaterialTheme {
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ColorScheme getColorScheme();
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.Shapes getShapes();
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.Typography getTypography();
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public final androidx.tv.material3.Shapes shapes;
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public final androidx.tv.material3.Typography typography;
+    field public static final androidx.tv.material3.MaterialTheme INSTANCE;
+  }
+
+  public final class MaterialThemeKt {
+  }
+
+  public final class ShapeDefaults {
+    method public androidx.compose.foundation.shape.CornerBasedShape getExtraLarge();
+    method public androidx.compose.foundation.shape.CornerBasedShape getExtraSmall();
+    method public androidx.compose.foundation.shape.CornerBasedShape getLarge();
+    method public androidx.compose.foundation.shape.CornerBasedShape getMedium();
+    method public androidx.compose.foundation.shape.CornerBasedShape getSmall();
+    property public final androidx.compose.foundation.shape.CornerBasedShape ExtraLarge;
+    property public final androidx.compose.foundation.shape.CornerBasedShape ExtraSmall;
+    property public final androidx.compose.foundation.shape.CornerBasedShape Large;
+    property public final androidx.compose.foundation.shape.CornerBasedShape Medium;
+    property public final androidx.compose.foundation.shape.CornerBasedShape Small;
+    field public static final androidx.tv.material3.ShapeDefaults INSTANCE;
+  }
+
+  @androidx.compose.runtime.Immutable public final class Shapes {
+    ctor public Shapes(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge);
+    method public androidx.tv.material3.Shapes copy(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge);
+    method public androidx.compose.foundation.shape.CornerBasedShape getExtraLarge();
+    method public androidx.compose.foundation.shape.CornerBasedShape getExtraSmall();
+    method public androidx.compose.foundation.shape.CornerBasedShape getLarge();
+    method public androidx.compose.foundation.shape.CornerBasedShape getMedium();
+    method public androidx.compose.foundation.shape.CornerBasedShape getSmall();
+    property public final androidx.compose.foundation.shape.CornerBasedShape extraLarge;
+    property public final androidx.compose.foundation.shape.CornerBasedShape extraSmall;
+    property public final androidx.compose.foundation.shape.CornerBasedShape large;
+    property public final androidx.compose.foundation.shape.CornerBasedShape medium;
+    property public final androidx.compose.foundation.shape.CornerBasedShape small;
+  }
+
+  public final class ShapesKt {
+  }
+
   public final class TabColors {
   }
 
   public final class TabDefaults {
-    method @androidx.compose.runtime.Composable public androidx.tv.material.TabColors pillIndicatorTabColors(optional long activeContentColor, optional long selectedContentColor, optional long focusedContentColor, optional long disabledActiveContentColor, optional long disabledSelectedContentColor);
-    method @androidx.compose.runtime.Composable public androidx.tv.material.TabColors underlinedIndicatorTabColors(optional long activeContentColor, optional long selectedContentColor, optional long focusedContentColor, optional long disabledActiveContentColor, optional long disabledSelectedContentColor);
-    field public static final androidx.tv.material.TabDefaults INSTANCE;
+    method @androidx.compose.runtime.Composable public androidx.tv.material3.TabColors pillIndicatorTabColors(optional long activeContentColor, optional long selectedContentColor, optional long focusedContentColor, optional long disabledActiveContentColor, optional long disabledSelectedContentColor);
+    method @androidx.compose.runtime.Composable public androidx.tv.material3.TabColors underlinedIndicatorTabColors(optional long activeContentColor, optional long selectedContentColor, optional long focusedContentColor, optional long disabledActiveContentColor, optional long disabledSelectedContentColor);
+    field public static final androidx.tv.material3.TabDefaults INSTANCE;
   }
 
   public final class TabKt {
-    method @androidx.compose.runtime.Composable public static void Tab(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onFocus, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional boolean enabled, optional androidx.tv.material.TabColors colors, 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 Tab(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onFocus, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional boolean enabled, optional androidx.tv.material3.TabColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
   }
 
   public final class TabRowDefaults {
@@ -29,28 +85,57 @@
     method @androidx.compose.runtime.Composable public long contentColor();
     method public long getContainerColor();
     property public final long ContainerColor;
-    field public static final androidx.tv.material.TabRowDefaults INSTANCE;
+    field public static final androidx.tv.material3.TabRowDefaults INSTANCE;
   }
 
   public final class TabRowKt {
     method @androidx.compose.runtime.Composable public static void TabRow(int selectedTabIndex, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function0<kotlin.Unit> separator, optional kotlin.jvm.functions.Function1<? super java.util.List<androidx.compose.ui.unit.DpRect>,kotlin.Unit> indicator, kotlin.jvm.functions.Function0<kotlin.Unit> tabs);
   }
 
-}
-
-package androidx.tv.material.carousel {
-
-  public final class CarouselItemKt {
+  public final class TextKt {
+    method @androidx.compose.runtime.Composable public static void ProvideTextStyle(androidx.compose.ui.text.TextStyle value, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void Text(String text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional long lineHeight, optional int overflow, optional boolean softWrap, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
+    method @androidx.compose.runtime.Composable public static void Text(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional long lineHeight, optional int overflow, optional boolean softWrap, optional int maxLines, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> getLocalTextStyle();
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> LocalTextStyle;
   }
 
-  public final class CarouselKt {
+  @androidx.compose.runtime.Immutable public final class Typography {
+    ctor public Typography(optional androidx.compose.ui.text.TextStyle displayLarge, optional androidx.compose.ui.text.TextStyle displayMedium, optional androidx.compose.ui.text.TextStyle displaySmall, optional androidx.compose.ui.text.TextStyle headlineLarge, optional androidx.compose.ui.text.TextStyle headlineMedium, optional androidx.compose.ui.text.TextStyle headlineSmall, optional androidx.compose.ui.text.TextStyle titleLarge, optional androidx.compose.ui.text.TextStyle titleMedium, optional androidx.compose.ui.text.TextStyle titleSmall, optional androidx.compose.ui.text.TextStyle bodyLarge, optional androidx.compose.ui.text.TextStyle bodyMedium, optional androidx.compose.ui.text.TextStyle bodySmall, optional androidx.compose.ui.text.TextStyle labelLarge, optional androidx.compose.ui.text.TextStyle labelMedium, optional androidx.compose.ui.text.TextStyle labelSmall);
+    method public androidx.tv.material3.Typography copy(optional androidx.compose.ui.text.TextStyle displayLarge, optional androidx.compose.ui.text.TextStyle displayMedium, optional androidx.compose.ui.text.TextStyle displaySmall, optional androidx.compose.ui.text.TextStyle headlineLarge, optional androidx.compose.ui.text.TextStyle headlineMedium, optional androidx.compose.ui.text.TextStyle headlineSmall, optional androidx.compose.ui.text.TextStyle titleLarge, optional androidx.compose.ui.text.TextStyle titleMedium, optional androidx.compose.ui.text.TextStyle titleSmall, optional androidx.compose.ui.text.TextStyle bodyLarge, optional androidx.compose.ui.text.TextStyle bodyMedium, optional androidx.compose.ui.text.TextStyle bodySmall, optional androidx.compose.ui.text.TextStyle labelLarge, optional androidx.compose.ui.text.TextStyle labelMedium, optional androidx.compose.ui.text.TextStyle labelSmall);
+    method public androidx.compose.ui.text.TextStyle getBodyLarge();
+    method public androidx.compose.ui.text.TextStyle getBodyMedium();
+    method public androidx.compose.ui.text.TextStyle getBodySmall();
+    method public androidx.compose.ui.text.TextStyle getDisplayLarge();
+    method public androidx.compose.ui.text.TextStyle getDisplayMedium();
+    method public androidx.compose.ui.text.TextStyle getDisplaySmall();
+    method public androidx.compose.ui.text.TextStyle getHeadlineLarge();
+    method public androidx.compose.ui.text.TextStyle getHeadlineMedium();
+    method public androidx.compose.ui.text.TextStyle getHeadlineSmall();
+    method public androidx.compose.ui.text.TextStyle getLabelLarge();
+    method public androidx.compose.ui.text.TextStyle getLabelMedium();
+    method public androidx.compose.ui.text.TextStyle getLabelSmall();
+    method public androidx.compose.ui.text.TextStyle getTitleLarge();
+    method public androidx.compose.ui.text.TextStyle getTitleMedium();
+    method public androidx.compose.ui.text.TextStyle getTitleSmall();
+    property public final androidx.compose.ui.text.TextStyle bodyLarge;
+    property public final androidx.compose.ui.text.TextStyle bodyMedium;
+    property public final androidx.compose.ui.text.TextStyle bodySmall;
+    property public final androidx.compose.ui.text.TextStyle displayLarge;
+    property public final androidx.compose.ui.text.TextStyle displayMedium;
+    property public final androidx.compose.ui.text.TextStyle displaySmall;
+    property public final androidx.compose.ui.text.TextStyle headlineLarge;
+    property public final androidx.compose.ui.text.TextStyle headlineMedium;
+    property public final androidx.compose.ui.text.TextStyle headlineSmall;
+    property public final androidx.compose.ui.text.TextStyle labelLarge;
+    property public final androidx.compose.ui.text.TextStyle labelMedium;
+    property public final androidx.compose.ui.text.TextStyle labelSmall;
+    property public final androidx.compose.ui.text.TextStyle titleLarge;
+    property public final androidx.compose.ui.text.TextStyle titleMedium;
+    property public final androidx.compose.ui.text.TextStyle titleSmall;
   }
 
-}
-
-package androidx.tv.material.immersivelist {
-
-  public final class ImmersiveListKt {
+  public final class TypographyKt {
   }
 
 }
diff --git a/tv/tv-material/api/public_plus_experimental_current.txt b/tv/tv-material/api/public_plus_experimental_current.txt
index a086e3e..475b193 100644
--- a/tv/tv-material/api/public_plus_experimental_current.txt
+++ b/tv/tv-material/api/public_plus_experimental_current.txt
@@ -1,28 +1,204 @@
 // Signature format: 4.0
-package androidx.tv.material {
+package androidx.tv.material3 {
 
   public final class BringIntoViewIfChildrenAreFocusedKt {
   }
 
+  @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CarouselDefaults {
+    method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public void IndicatorRow(int slideCount, int activeSlideIndex, optional androidx.compose.ui.Modifier modifier, optional float spacing, optional kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> indicator);
+    method public androidx.compose.animation.EnterTransition getEnterTransition();
+    method public androidx.compose.animation.ExitTransition getExitTransition();
+    property public final androidx.compose.animation.EnterTransition EnterTransition;
+    property public final androidx.compose.animation.ExitTransition ExitTransition;
+    field public static final androidx.tv.material3.CarouselDefaults INSTANCE;
+    field public static final long TimeToDisplaySlideMillis = 5000L; // 0x1388L
+  }
+
+  @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CarouselItemDefaults {
+    method public androidx.compose.animation.EnterTransition getOverlayEnterTransition();
+    method public androidx.compose.animation.ExitTransition getOverlayExitTransition();
+    property public final androidx.compose.animation.EnterTransition OverlayEnterTransition;
+    property public final androidx.compose.animation.ExitTransition OverlayExitTransition;
+    field public static final androidx.tv.material3.CarouselItemDefaults INSTANCE;
+    field public static final long OverlayEnterTransitionStartDelayMillis = 200L; // 0xc8L
+  }
+
+  public final class CarouselItemKt {
+    method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void CarouselItem(kotlin.jvm.functions.Function0<kotlin.Unit> background, optional androidx.compose.ui.Modifier modifier, optional long overlayEnterTransitionStartDelayMillis, optional androidx.compose.animation.EnterTransition overlayEnterTransition, optional androidx.compose.animation.ExitTransition overlayExitTransition, kotlin.jvm.functions.Function0<kotlin.Unit> overlay);
+  }
+
+  public final class CarouselKt {
+    method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Carousel(int slideCount, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.CarouselState carouselState, optional long timeToDisplaySlideMillis, optional androidx.compose.animation.EnterTransition enterTransition, optional androidx.compose.animation.ExitTransition exitTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> carouselIndicator, kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> content);
+  }
+
+  @androidx.compose.runtime.Stable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CarouselState {
+    ctor public CarouselState(optional int initialActiveSlideIndex);
+    method public int getActiveSlideIndex();
+    method public androidx.tv.material3.ScrollPauseHandle pauseAutoScroll(int slideIndex);
+    property public final int activeSlideIndex;
+  }
+
+  @androidx.compose.runtime.Stable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ColorScheme {
+    ctor public ColorScheme(long primary, long onPrimary, long primaryContainer, long onPrimaryContainer, long inversePrimary, long secondary, long onSecondary, long secondaryContainer, long onSecondaryContainer, long tertiary, long onTertiary, long tertiaryContainer, long onTertiaryContainer, long background, long onBackground, long surface, long onSurface, long surfaceVariant, long onSurfaceVariant, long surfaceTint, long inverseSurface, long inverseOnSurface, long error, long onError, long errorContainer, long onErrorContainer, long outline, long outlineVariant, long scrim);
+    method public androidx.tv.material3.ColorScheme copy(optional long primary, optional long onPrimary, optional long primaryContainer, optional long onPrimaryContainer, optional long inversePrimary, optional long secondary, optional long onSecondary, optional long secondaryContainer, optional long onSecondaryContainer, optional long tertiary, optional long onTertiary, optional long tertiaryContainer, optional long onTertiaryContainer, optional long background, optional long onBackground, optional long surface, optional long onSurface, optional long surfaceVariant, optional long onSurfaceVariant, optional long surfaceTint, optional long inverseSurface, optional long inverseOnSurface, optional long error, optional long onError, optional long errorContainer, optional long onErrorContainer, optional long outline, optional long outlineVariant, optional long scrim);
+    method public long getBackground();
+    method public long getError();
+    method public long getErrorContainer();
+    method public long getInverseOnSurface();
+    method public long getInversePrimary();
+    method public long getInverseSurface();
+    method public long getOnBackground();
+    method public long getOnError();
+    method public long getOnErrorContainer();
+    method public long getOnPrimary();
+    method public long getOnPrimaryContainer();
+    method public long getOnSecondary();
+    method public long getOnSecondaryContainer();
+    method public long getOnSurface();
+    method public long getOnSurfaceVariant();
+    method public long getOnTertiary();
+    method public long getOnTertiaryContainer();
+    method public long getOutline();
+    method public long getOutlineVariant();
+    method public long getPrimary();
+    method public long getPrimaryContainer();
+    method public long getScrim();
+    method public long getSecondary();
+    method public long getSecondaryContainer();
+    method public long getSurface();
+    method public long getSurfaceTint();
+    method public long getSurfaceVariant();
+    method public long getTertiary();
+    method public long getTertiaryContainer();
+    property public final long background;
+    property public final long error;
+    property public final long errorContainer;
+    property public final long inverseOnSurface;
+    property public final long inversePrimary;
+    property public final long inverseSurface;
+    property public final long onBackground;
+    property public final long onError;
+    property public final long onErrorContainer;
+    property public final long onPrimary;
+    property public final long onPrimaryContainer;
+    property public final long onSecondary;
+    property public final long onSecondaryContainer;
+    property public final long onSurface;
+    property public final long onSurfaceVariant;
+    property public final long onTertiary;
+    property public final long onTertiaryContainer;
+    property public final long outline;
+    property public final long outlineVariant;
+    property public final long primary;
+    property public final long primaryContainer;
+    property public final long scrim;
+    property public final long secondary;
+    property public final long secondaryContainer;
+    property public final long surface;
+    property public final long surfaceTint;
+    property public final long surfaceVariant;
+    property public final long tertiary;
+    property public final long tertiaryContainer;
+  }
+
+  public final class ColorSchemeKt {
+    method @androidx.tv.material3.ExperimentalTvMaterial3Api public static long contentColorFor(androidx.tv.material3.ColorScheme, long backgroundColor);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static long contentColorFor(long backgroundColor);
+    method @androidx.tv.material3.ExperimentalTvMaterial3Api public static androidx.tv.material3.ColorScheme darkColorScheme(optional long primary, optional long onPrimary, optional long primaryContainer, optional long onPrimaryContainer, optional long inversePrimary, optional long secondary, optional long onSecondary, optional long secondaryContainer, optional long onSecondaryContainer, optional long tertiary, optional long onTertiary, optional long tertiaryContainer, optional long onTertiaryContainer, optional long background, optional long onBackground, optional long surface, optional long onSurface, optional long surfaceVariant, optional long onSurfaceVariant, optional long surfaceTint, optional long inverseSurface, optional long inverseOnSurface, optional long error, optional long onError, optional long errorContainer, optional long onErrorContainer, optional long outline, optional long outlineVariant, optional long scrim);
+    method @androidx.tv.material3.ExperimentalTvMaterial3Api public static androidx.tv.material3.ColorScheme lightColorScheme(optional long primary, optional long onPrimary, optional long primaryContainer, optional long onPrimaryContainer, optional long inversePrimary, optional long secondary, optional long onSecondary, optional long secondaryContainer, optional long onSecondaryContainer, optional long tertiary, optional long onTertiary, optional long tertiaryContainer, optional long onTertiaryContainer, optional long background, optional long onBackground, optional long surface, optional long onSurface, optional long surfaceVariant, optional long onSurfaceVariant, optional long surfaceTint, optional long inverseSurface, optional long inverseOnSurface, optional long error, optional long onError, optional long errorContainer, optional long onErrorContainer, optional long outline, optional long outlineVariant, optional long scrim);
+    method @androidx.tv.material3.ExperimentalTvMaterial3Api public static long surfaceColorAtElevation(androidx.tv.material3.ColorScheme, float elevation);
+  }
+
   public final class ContentColorKt {
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> getLocalContentColor();
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> LocalContentColor;
   }
 
-  @kotlin.RequiresOptIn(message="This tv-material API is experimental and likely to change or be removed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalTvMaterialApi {
+  @kotlin.RequiresOptIn(message="This tv-material API is experimental and likely to change or be removed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalTvMaterial3Api {
+  }
+
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ImmersiveListBackgroundScope implements androidx.compose.foundation.layout.BoxScope {
+    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public void AnimatedContent(int targetState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentScope<java.lang.Integer>,androidx.compose.animation.ContentTransform> transitionSpec, optional androidx.compose.ui.Alignment contentAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedVisibilityScope,? super java.lang.Integer,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional String label, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
+  }
+
+  @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ImmersiveListDefaults {
+    method public androidx.compose.animation.EnterTransition getEnterTransition();
+    method public androidx.compose.animation.ExitTransition getExitTransition();
+    property public final androidx.compose.animation.EnterTransition EnterTransition;
+    property public final androidx.compose.animation.ExitTransition ExitTransition;
+    field public static final androidx.tv.material3.ImmersiveListDefaults INSTANCE;
+  }
+
+  public final class ImmersiveListKt {
+    method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void ImmersiveList(kotlin.jvm.functions.Function3<? super androidx.tv.material3.ImmersiveListBackgroundScope,? super java.lang.Integer,? super java.lang.Boolean,kotlin.Unit> background, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment listAlignment, kotlin.jvm.functions.Function1<? super androidx.tv.material3.ImmersiveListScope,kotlin.Unit> list);
+  }
+
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ImmersiveListScope {
+    method public androidx.compose.ui.Modifier immersiveListItem(androidx.compose.ui.Modifier, int index);
+  }
+
+  public final class MaterialTheme {
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ColorScheme getColorScheme();
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.Shapes getShapes();
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.Typography getTypography();
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public final androidx.tv.material3.ColorScheme colorScheme;
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public final androidx.tv.material3.Shapes shapes;
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public final androidx.tv.material3.Typography typography;
+    field public static final androidx.tv.material3.MaterialTheme INSTANCE;
+  }
+
+  public final class MaterialThemeKt {
+    method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void MaterialTheme(optional androidx.tv.material3.ColorScheme colorScheme, optional androidx.tv.material3.Shapes shapes, optional androidx.tv.material3.Typography typography, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  @androidx.tv.material3.ExperimentalTvMaterial3Api public sealed interface ScrollPauseHandle {
+    method public void resumeAutoScroll();
+  }
+
+  public final class ShapeDefaults {
+    method public androidx.compose.foundation.shape.CornerBasedShape getExtraLarge();
+    method public androidx.compose.foundation.shape.CornerBasedShape getExtraSmall();
+    method public androidx.compose.foundation.shape.CornerBasedShape getLarge();
+    method public androidx.compose.foundation.shape.CornerBasedShape getMedium();
+    method public androidx.compose.foundation.shape.CornerBasedShape getSmall();
+    property public final androidx.compose.foundation.shape.CornerBasedShape ExtraLarge;
+    property public final androidx.compose.foundation.shape.CornerBasedShape ExtraSmall;
+    property public final androidx.compose.foundation.shape.CornerBasedShape Large;
+    property public final androidx.compose.foundation.shape.CornerBasedShape Medium;
+    property public final androidx.compose.foundation.shape.CornerBasedShape Small;
+    field public static final androidx.tv.material3.ShapeDefaults INSTANCE;
+  }
+
+  @androidx.compose.runtime.Immutable public final class Shapes {
+    ctor public Shapes(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge);
+    method public androidx.tv.material3.Shapes copy(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge);
+    method public androidx.compose.foundation.shape.CornerBasedShape getExtraLarge();
+    method public androidx.compose.foundation.shape.CornerBasedShape getExtraSmall();
+    method public androidx.compose.foundation.shape.CornerBasedShape getLarge();
+    method public androidx.compose.foundation.shape.CornerBasedShape getMedium();
+    method public androidx.compose.foundation.shape.CornerBasedShape getSmall();
+    property public final androidx.compose.foundation.shape.CornerBasedShape extraLarge;
+    property public final androidx.compose.foundation.shape.CornerBasedShape extraSmall;
+    property public final androidx.compose.foundation.shape.CornerBasedShape large;
+    property public final androidx.compose.foundation.shape.CornerBasedShape medium;
+    property public final androidx.compose.foundation.shape.CornerBasedShape small;
+  }
+
+  public final class ShapesKt {
   }
 
   public final class TabColors {
   }
 
   public final class TabDefaults {
-    method @androidx.compose.runtime.Composable public androidx.tv.material.TabColors pillIndicatorTabColors(optional long activeContentColor, optional long selectedContentColor, optional long focusedContentColor, optional long disabledActiveContentColor, optional long disabledSelectedContentColor);
-    method @androidx.compose.runtime.Composable public androidx.tv.material.TabColors underlinedIndicatorTabColors(optional long activeContentColor, optional long selectedContentColor, optional long focusedContentColor, optional long disabledActiveContentColor, optional long disabledSelectedContentColor);
-    field public static final androidx.tv.material.TabDefaults INSTANCE;
+    method @androidx.compose.runtime.Composable public androidx.tv.material3.TabColors pillIndicatorTabColors(optional long activeContentColor, optional long selectedContentColor, optional long focusedContentColor, optional long disabledActiveContentColor, optional long disabledSelectedContentColor);
+    method @androidx.compose.runtime.Composable public androidx.tv.material3.TabColors underlinedIndicatorTabColors(optional long activeContentColor, optional long selectedContentColor, optional long focusedContentColor, optional long disabledActiveContentColor, optional long disabledSelectedContentColor);
+    field public static final androidx.tv.material3.TabDefaults INSTANCE;
   }
 
   public final class TabKt {
-    method @androidx.compose.runtime.Composable public static void Tab(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onFocus, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional boolean enabled, optional androidx.tv.material.TabColors colors, 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 Tab(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onFocus, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional boolean enabled, optional androidx.tv.material3.TabColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
   }
 
   public final class TabRowDefaults {
@@ -32,78 +208,57 @@
     method @androidx.compose.runtime.Composable public long contentColor();
     method public long getContainerColor();
     property public final long ContainerColor;
-    field public static final androidx.tv.material.TabRowDefaults INSTANCE;
+    field public static final androidx.tv.material3.TabRowDefaults INSTANCE;
   }
 
   public final class TabRowKt {
     method @androidx.compose.runtime.Composable public static void TabRow(int selectedTabIndex, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function0<kotlin.Unit> separator, optional kotlin.jvm.functions.Function1<? super java.util.List<androidx.compose.ui.unit.DpRect>,kotlin.Unit> indicator, kotlin.jvm.functions.Function0<kotlin.Unit> tabs);
   }
 
-}
-
-package androidx.tv.material.carousel {
-
-  @androidx.tv.material.ExperimentalTvMaterialApi public final class CarouselDefaults {
-    method @androidx.compose.runtime.Composable @androidx.tv.material.ExperimentalTvMaterialApi public void IndicatorRow(int slideCount, int activeSlideIndex, optional androidx.compose.ui.Modifier modifier, optional float spacing, optional kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> indicator);
-    method public androidx.compose.animation.EnterTransition getEnterTransition();
-    method public androidx.compose.animation.ExitTransition getExitTransition();
-    property public final androidx.compose.animation.EnterTransition EnterTransition;
-    property public final androidx.compose.animation.ExitTransition ExitTransition;
-    field public static final androidx.tv.material.carousel.CarouselDefaults INSTANCE;
-    field public static final long TimeToDisplaySlideMillis = 5000L; // 0x1388L
+  public final class TextKt {
+    method @androidx.compose.runtime.Composable public static void ProvideTextStyle(androidx.compose.ui.text.TextStyle value, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void Text(String text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional long lineHeight, optional int overflow, optional boolean softWrap, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
+    method @androidx.compose.runtime.Composable public static void Text(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional long lineHeight, optional int overflow, optional boolean softWrap, optional int maxLines, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> getLocalTextStyle();
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> LocalTextStyle;
   }
 
-  @androidx.tv.material.ExperimentalTvMaterialApi public final class CarouselItemDefaults {
-    method public androidx.compose.animation.EnterTransition getOverlayEnterTransition();
-    method public androidx.compose.animation.ExitTransition getOverlayExitTransition();
-    property public final androidx.compose.animation.EnterTransition OverlayEnterTransition;
-    property public final androidx.compose.animation.ExitTransition OverlayExitTransition;
-    field public static final androidx.tv.material.carousel.CarouselItemDefaults INSTANCE;
-    field public static final long OverlayEnterTransitionStartDelayMillis = 200L; // 0xc8L
+  @androidx.compose.runtime.Immutable public final class Typography {
+    ctor public Typography(optional androidx.compose.ui.text.TextStyle displayLarge, optional androidx.compose.ui.text.TextStyle displayMedium, optional androidx.compose.ui.text.TextStyle displaySmall, optional androidx.compose.ui.text.TextStyle headlineLarge, optional androidx.compose.ui.text.TextStyle headlineMedium, optional androidx.compose.ui.text.TextStyle headlineSmall, optional androidx.compose.ui.text.TextStyle titleLarge, optional androidx.compose.ui.text.TextStyle titleMedium, optional androidx.compose.ui.text.TextStyle titleSmall, optional androidx.compose.ui.text.TextStyle bodyLarge, optional androidx.compose.ui.text.TextStyle bodyMedium, optional androidx.compose.ui.text.TextStyle bodySmall, optional androidx.compose.ui.text.TextStyle labelLarge, optional androidx.compose.ui.text.TextStyle labelMedium, optional androidx.compose.ui.text.TextStyle labelSmall);
+    method public androidx.tv.material3.Typography copy(optional androidx.compose.ui.text.TextStyle displayLarge, optional androidx.compose.ui.text.TextStyle displayMedium, optional androidx.compose.ui.text.TextStyle displaySmall, optional androidx.compose.ui.text.TextStyle headlineLarge, optional androidx.compose.ui.text.TextStyle headlineMedium, optional androidx.compose.ui.text.TextStyle headlineSmall, optional androidx.compose.ui.text.TextStyle titleLarge, optional androidx.compose.ui.text.TextStyle titleMedium, optional androidx.compose.ui.text.TextStyle titleSmall, optional androidx.compose.ui.text.TextStyle bodyLarge, optional androidx.compose.ui.text.TextStyle bodyMedium, optional androidx.compose.ui.text.TextStyle bodySmall, optional androidx.compose.ui.text.TextStyle labelLarge, optional androidx.compose.ui.text.TextStyle labelMedium, optional androidx.compose.ui.text.TextStyle labelSmall);
+    method public androidx.compose.ui.text.TextStyle getBodyLarge();
+    method public androidx.compose.ui.text.TextStyle getBodyMedium();
+    method public androidx.compose.ui.text.TextStyle getBodySmall();
+    method public androidx.compose.ui.text.TextStyle getDisplayLarge();
+    method public androidx.compose.ui.text.TextStyle getDisplayMedium();
+    method public androidx.compose.ui.text.TextStyle getDisplaySmall();
+    method public androidx.compose.ui.text.TextStyle getHeadlineLarge();
+    method public androidx.compose.ui.text.TextStyle getHeadlineMedium();
+    method public androidx.compose.ui.text.TextStyle getHeadlineSmall();
+    method public androidx.compose.ui.text.TextStyle getLabelLarge();
+    method public androidx.compose.ui.text.TextStyle getLabelMedium();
+    method public androidx.compose.ui.text.TextStyle getLabelSmall();
+    method public androidx.compose.ui.text.TextStyle getTitleLarge();
+    method public androidx.compose.ui.text.TextStyle getTitleMedium();
+    method public androidx.compose.ui.text.TextStyle getTitleSmall();
+    property public final androidx.compose.ui.text.TextStyle bodyLarge;
+    property public final androidx.compose.ui.text.TextStyle bodyMedium;
+    property public final androidx.compose.ui.text.TextStyle bodySmall;
+    property public final androidx.compose.ui.text.TextStyle displayLarge;
+    property public final androidx.compose.ui.text.TextStyle displayMedium;
+    property public final androidx.compose.ui.text.TextStyle displaySmall;
+    property public final androidx.compose.ui.text.TextStyle headlineLarge;
+    property public final androidx.compose.ui.text.TextStyle headlineMedium;
+    property public final androidx.compose.ui.text.TextStyle headlineSmall;
+    property public final androidx.compose.ui.text.TextStyle labelLarge;
+    property public final androidx.compose.ui.text.TextStyle labelMedium;
+    property public final androidx.compose.ui.text.TextStyle labelSmall;
+    property public final androidx.compose.ui.text.TextStyle titleLarge;
+    property public final androidx.compose.ui.text.TextStyle titleMedium;
+    property public final androidx.compose.ui.text.TextStyle titleSmall;
   }
 
-  public final class CarouselItemKt {
-    method @androidx.compose.runtime.Composable @androidx.tv.material.ExperimentalTvMaterialApi public static void CarouselItem(kotlin.jvm.functions.Function0<kotlin.Unit> background, optional androidx.compose.ui.Modifier modifier, optional long overlayEnterTransitionStartDelayMillis, optional androidx.compose.animation.EnterTransition overlayEnterTransition, optional androidx.compose.animation.ExitTransition overlayExitTransition, kotlin.jvm.functions.Function0<kotlin.Unit> overlay);
-  }
-
-  public final class CarouselKt {
-    method @androidx.compose.runtime.Composable @androidx.tv.material.ExperimentalTvMaterialApi public static void Carousel(int slideCount, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material.carousel.CarouselState carouselState, optional long timeToDisplaySlideMillis, optional androidx.compose.animation.EnterTransition enterTransition, optional androidx.compose.animation.ExitTransition exitTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> carouselIndicator, kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> content);
-  }
-
-  @androidx.compose.runtime.Stable @androidx.tv.material.ExperimentalTvMaterialApi public final class CarouselState {
-    ctor public CarouselState(optional int initialActiveSlideIndex);
-    method public int getActiveSlideIndex();
-    method public androidx.tv.material.carousel.ScrollPauseHandle pauseAutoScroll(int slideIndex);
-    property public final int activeSlideIndex;
-  }
-
-  @androidx.tv.material.ExperimentalTvMaterialApi public sealed interface ScrollPauseHandle {
-    method public void resumeAutoScroll();
-  }
-
-}
-
-package androidx.tv.material.immersivelist {
-
-  @androidx.compose.runtime.Immutable @androidx.tv.material.ExperimentalTvMaterialApi public final class ImmersiveListBackgroundScope implements androidx.compose.foundation.layout.BoxScope {
-    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public void AnimatedContent(int targetState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentScope<java.lang.Integer>,androidx.compose.animation.ContentTransform> transitionSpec, optional androidx.compose.ui.Alignment contentAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedVisibilityScope,? super java.lang.Integer,kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable public void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional String label, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
-  }
-
-  @androidx.tv.material.ExperimentalTvMaterialApi public final class ImmersiveListDefaults {
-    method public androidx.compose.animation.EnterTransition getEnterTransition();
-    method public androidx.compose.animation.ExitTransition getExitTransition();
-    property public final androidx.compose.animation.EnterTransition EnterTransition;
-    property public final androidx.compose.animation.ExitTransition ExitTransition;
-    field public static final androidx.tv.material.immersivelist.ImmersiveListDefaults INSTANCE;
-  }
-
-  public final class ImmersiveListKt {
-    method @androidx.compose.runtime.Composable @androidx.tv.material.ExperimentalTvMaterialApi public static void ImmersiveList(kotlin.jvm.functions.Function3<? super androidx.tv.material.immersivelist.ImmersiveListBackgroundScope,? super java.lang.Integer,? super java.lang.Boolean,kotlin.Unit> background, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.ui.Alignment listAlignment, kotlin.jvm.functions.Function1<? super androidx.tv.material.immersivelist.ImmersiveListScope,kotlin.Unit> list);
-  }
-
-  @androidx.compose.runtime.Immutable @androidx.tv.material.ExperimentalTvMaterialApi public final class ImmersiveListScope {
-    method public androidx.compose.ui.Modifier focusableItem(androidx.compose.ui.Modifier, int index);
+  public final class TypographyKt {
   }
 
 }
diff --git a/tv/tv-material/api/restricted_current.txt b/tv/tv-material/api/restricted_current.txt
index 0c788a4..0b46921 100644
--- a/tv/tv-material/api/restricted_current.txt
+++ b/tv/tv-material/api/restricted_current.txt
@@ -1,25 +1,81 @@
 // Signature format: 4.0
-package androidx.tv.material {
+package androidx.tv.material3 {
 
   public final class BringIntoViewIfChildrenAreFocusedKt {
   }
 
+  public final class CarouselItemKt {
+  }
+
+  public final class CarouselKt {
+  }
+
+  public final class ColorSchemeKt {
+  }
+
   public final class ContentColorKt {
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> getLocalContentColor();
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> LocalContentColor;
   }
 
+  public final class ImmersiveListKt {
+  }
+
+  public final class MaterialTheme {
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ColorScheme getColorScheme();
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.Shapes getShapes();
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.Typography getTypography();
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public final androidx.tv.material3.Shapes shapes;
+    property @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public final androidx.tv.material3.Typography typography;
+    field public static final androidx.tv.material3.MaterialTheme INSTANCE;
+  }
+
+  public final class MaterialThemeKt {
+  }
+
+  public final class ShapeDefaults {
+    method public androidx.compose.foundation.shape.CornerBasedShape getExtraLarge();
+    method public androidx.compose.foundation.shape.CornerBasedShape getExtraSmall();
+    method public androidx.compose.foundation.shape.CornerBasedShape getLarge();
+    method public androidx.compose.foundation.shape.CornerBasedShape getMedium();
+    method public androidx.compose.foundation.shape.CornerBasedShape getSmall();
+    property public final androidx.compose.foundation.shape.CornerBasedShape ExtraLarge;
+    property public final androidx.compose.foundation.shape.CornerBasedShape ExtraSmall;
+    property public final androidx.compose.foundation.shape.CornerBasedShape Large;
+    property public final androidx.compose.foundation.shape.CornerBasedShape Medium;
+    property public final androidx.compose.foundation.shape.CornerBasedShape Small;
+    field public static final androidx.tv.material3.ShapeDefaults INSTANCE;
+  }
+
+  @androidx.compose.runtime.Immutable public final class Shapes {
+    ctor public Shapes(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge);
+    method public androidx.tv.material3.Shapes copy(optional androidx.compose.foundation.shape.CornerBasedShape extraSmall, optional androidx.compose.foundation.shape.CornerBasedShape small, optional androidx.compose.foundation.shape.CornerBasedShape medium, optional androidx.compose.foundation.shape.CornerBasedShape large, optional androidx.compose.foundation.shape.CornerBasedShape extraLarge);
+    method public androidx.compose.foundation.shape.CornerBasedShape getExtraLarge();
+    method public androidx.compose.foundation.shape.CornerBasedShape getExtraSmall();
+    method public androidx.compose.foundation.shape.CornerBasedShape getLarge();
+    method public androidx.compose.foundation.shape.CornerBasedShape getMedium();
+    method public androidx.compose.foundation.shape.CornerBasedShape getSmall();
+    property public final androidx.compose.foundation.shape.CornerBasedShape extraLarge;
+    property public final androidx.compose.foundation.shape.CornerBasedShape extraSmall;
+    property public final androidx.compose.foundation.shape.CornerBasedShape large;
+    property public final androidx.compose.foundation.shape.CornerBasedShape medium;
+    property public final androidx.compose.foundation.shape.CornerBasedShape small;
+  }
+
+  public final class ShapesKt {
+  }
+
   public final class TabColors {
   }
 
   public final class TabDefaults {
-    method @androidx.compose.runtime.Composable public androidx.tv.material.TabColors pillIndicatorTabColors(optional long activeContentColor, optional long selectedContentColor, optional long focusedContentColor, optional long disabledActiveContentColor, optional long disabledSelectedContentColor);
-    method @androidx.compose.runtime.Composable public androidx.tv.material.TabColors underlinedIndicatorTabColors(optional long activeContentColor, optional long selectedContentColor, optional long focusedContentColor, optional long disabledActiveContentColor, optional long disabledSelectedContentColor);
-    field public static final androidx.tv.material.TabDefaults INSTANCE;
+    method @androidx.compose.runtime.Composable public androidx.tv.material3.TabColors pillIndicatorTabColors(optional long activeContentColor, optional long selectedContentColor, optional long focusedContentColor, optional long disabledActiveContentColor, optional long disabledSelectedContentColor);
+    method @androidx.compose.runtime.Composable public androidx.tv.material3.TabColors underlinedIndicatorTabColors(optional long activeContentColor, optional long selectedContentColor, optional long focusedContentColor, optional long disabledActiveContentColor, optional long disabledSelectedContentColor);
+    field public static final androidx.tv.material3.TabDefaults INSTANCE;
   }
 
   public final class TabKt {
-    method @androidx.compose.runtime.Composable public static void Tab(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onFocus, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional boolean enabled, optional androidx.tv.material.TabColors colors, 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 Tab(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit> onFocus, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional boolean enabled, optional androidx.tv.material3.TabColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
   }
 
   public final class TabRowDefaults {
@@ -29,28 +85,57 @@
     method @androidx.compose.runtime.Composable public long contentColor();
     method public long getContainerColor();
     property public final long ContainerColor;
-    field public static final androidx.tv.material.TabRowDefaults INSTANCE;
+    field public static final androidx.tv.material3.TabRowDefaults INSTANCE;
   }
 
   public final class TabRowKt {
     method @androidx.compose.runtime.Composable public static void TabRow(int selectedTabIndex, optional androidx.compose.ui.Modifier modifier, optional long containerColor, optional long contentColor, optional kotlin.jvm.functions.Function0<kotlin.Unit> separator, optional kotlin.jvm.functions.Function1<? super java.util.List<androidx.compose.ui.unit.DpRect>,kotlin.Unit> indicator, kotlin.jvm.functions.Function0<kotlin.Unit> tabs);
   }
 
-}
-
-package androidx.tv.material.carousel {
-
-  public final class CarouselItemKt {
+  public final class TextKt {
+    method @androidx.compose.runtime.Composable public static void ProvideTextStyle(androidx.compose.ui.text.TextStyle value, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void Text(String text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional long lineHeight, optional int overflow, optional boolean softWrap, optional int maxLines, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
+    method @androidx.compose.runtime.Composable public static void Text(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.Modifier modifier, optional long color, optional long fontSize, optional androidx.compose.ui.text.font.FontStyle? fontStyle, optional androidx.compose.ui.text.font.FontWeight? fontWeight, optional androidx.compose.ui.text.font.FontFamily? fontFamily, optional long letterSpacing, optional androidx.compose.ui.text.style.TextDecoration? textDecoration, optional androidx.compose.ui.text.style.TextAlign? textAlign, optional long lineHeight, optional int overflow, optional boolean softWrap, optional int maxLines, optional java.util.Map<java.lang.String,androidx.compose.foundation.text.InlineTextContent> inlineContent, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout, optional androidx.compose.ui.text.TextStyle style);
+    method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> getLocalTextStyle();
+    property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> LocalTextStyle;
   }
 
-  public final class CarouselKt {
+  @androidx.compose.runtime.Immutable public final class Typography {
+    ctor public Typography(optional androidx.compose.ui.text.TextStyle displayLarge, optional androidx.compose.ui.text.TextStyle displayMedium, optional androidx.compose.ui.text.TextStyle displaySmall, optional androidx.compose.ui.text.TextStyle headlineLarge, optional androidx.compose.ui.text.TextStyle headlineMedium, optional androidx.compose.ui.text.TextStyle headlineSmall, optional androidx.compose.ui.text.TextStyle titleLarge, optional androidx.compose.ui.text.TextStyle titleMedium, optional androidx.compose.ui.text.TextStyle titleSmall, optional androidx.compose.ui.text.TextStyle bodyLarge, optional androidx.compose.ui.text.TextStyle bodyMedium, optional androidx.compose.ui.text.TextStyle bodySmall, optional androidx.compose.ui.text.TextStyle labelLarge, optional androidx.compose.ui.text.TextStyle labelMedium, optional androidx.compose.ui.text.TextStyle labelSmall);
+    method public androidx.tv.material3.Typography copy(optional androidx.compose.ui.text.TextStyle displayLarge, optional androidx.compose.ui.text.TextStyle displayMedium, optional androidx.compose.ui.text.TextStyle displaySmall, optional androidx.compose.ui.text.TextStyle headlineLarge, optional androidx.compose.ui.text.TextStyle headlineMedium, optional androidx.compose.ui.text.TextStyle headlineSmall, optional androidx.compose.ui.text.TextStyle titleLarge, optional androidx.compose.ui.text.TextStyle titleMedium, optional androidx.compose.ui.text.TextStyle titleSmall, optional androidx.compose.ui.text.TextStyle bodyLarge, optional androidx.compose.ui.text.TextStyle bodyMedium, optional androidx.compose.ui.text.TextStyle bodySmall, optional androidx.compose.ui.text.TextStyle labelLarge, optional androidx.compose.ui.text.TextStyle labelMedium, optional androidx.compose.ui.text.TextStyle labelSmall);
+    method public androidx.compose.ui.text.TextStyle getBodyLarge();
+    method public androidx.compose.ui.text.TextStyle getBodyMedium();
+    method public androidx.compose.ui.text.TextStyle getBodySmall();
+    method public androidx.compose.ui.text.TextStyle getDisplayLarge();
+    method public androidx.compose.ui.text.TextStyle getDisplayMedium();
+    method public androidx.compose.ui.text.TextStyle getDisplaySmall();
+    method public androidx.compose.ui.text.TextStyle getHeadlineLarge();
+    method public androidx.compose.ui.text.TextStyle getHeadlineMedium();
+    method public androidx.compose.ui.text.TextStyle getHeadlineSmall();
+    method public androidx.compose.ui.text.TextStyle getLabelLarge();
+    method public androidx.compose.ui.text.TextStyle getLabelMedium();
+    method public androidx.compose.ui.text.TextStyle getLabelSmall();
+    method public androidx.compose.ui.text.TextStyle getTitleLarge();
+    method public androidx.compose.ui.text.TextStyle getTitleMedium();
+    method public androidx.compose.ui.text.TextStyle getTitleSmall();
+    property public final androidx.compose.ui.text.TextStyle bodyLarge;
+    property public final androidx.compose.ui.text.TextStyle bodyMedium;
+    property public final androidx.compose.ui.text.TextStyle bodySmall;
+    property public final androidx.compose.ui.text.TextStyle displayLarge;
+    property public final androidx.compose.ui.text.TextStyle displayMedium;
+    property public final androidx.compose.ui.text.TextStyle displaySmall;
+    property public final androidx.compose.ui.text.TextStyle headlineLarge;
+    property public final androidx.compose.ui.text.TextStyle headlineMedium;
+    property public final androidx.compose.ui.text.TextStyle headlineSmall;
+    property public final androidx.compose.ui.text.TextStyle labelLarge;
+    property public final androidx.compose.ui.text.TextStyle labelMedium;
+    property public final androidx.compose.ui.text.TextStyle labelSmall;
+    property public final androidx.compose.ui.text.TextStyle titleLarge;
+    property public final androidx.compose.ui.text.TextStyle titleMedium;
+    property public final androidx.compose.ui.text.TextStyle titleSmall;
   }
 
-}
-
-package androidx.tv.material.immersivelist {
-
-  public final class ImmersiveListKt {
+  public final class TypographyKt {
   }
 
 }
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material/carousel/CarouselItemTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material/carousel/CarouselItemTest.kt
deleted file mode 100644
index 25d05ef..0000000
--- a/tv/tv-material/src/androidTest/java/androidx/tv/material/carousel/CarouselItemTest.kt
+++ /dev/null
@@ -1,69 +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.tv.material.carousel
-
-import androidx.compose.foundation.background
-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.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.unit.dp
-import androidx.tv.material.ExperimentalTvMaterialApi
-import org.junit.Rule
-import org.junit.Test
-
-class CarouselItemTest {
-    @get:Rule
-    val rule = createComposeRule()
-
-    @OptIn(ExperimentalTvMaterialApi::class)
-    @Test
-    fun carouselItem_overlayVisibleAfterRenderTime() {
-        val overlayEnterTransitionStartDelay: Long = 2000
-        val overlayTag = "overlay"
-        val backgroundTag = "background"
-        rule.setContent {
-            CarouselItem(
-                overlayEnterTransitionStartDelayMillis = overlayEnterTransitionStartDelay,
-                background = {
-                    Box(
-                        Modifier
-                            .testTag(backgroundTag)
-                            .size(200.dp)
-                            .background(Color.Blue)) }) {
-                Box(
-                    Modifier
-                        .testTag(overlayTag)
-                        .size(50.dp)
-                        .background(Color.Red))
-            }
-        }
-
-        // only background is visible
-        rule.onNodeWithTag(backgroundTag).assertExists()
-        rule.onNodeWithTag(overlayTag).assertDoesNotExist()
-
-        // advance clock by `overlayEnterTransitionStartDelay`
-        rule.mainClock.advanceTimeBy(overlayEnterTransitionStartDelay)
-
-        rule.onNodeWithTag(backgroundTag).assertExists()
-        rule.onNodeWithTag(overlayTag).assertExists()
-    }
-}
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselItemTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselItemTest.kt
new file mode 100644
index 0000000..bc41a0f
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselItemTest.kt
@@ -0,0 +1,137 @@
+/*
+ * 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.tv.material3
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.key.NativeKeyEvent
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.test.assertIsFocused
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performSemanticsAction
+import androidx.compose.ui.unit.dp
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Rule
+import org.junit.Test
+
+class CarouselItemTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @OptIn(ExperimentalTvMaterial3Api::class)
+    @Test
+    fun carouselItem_overlayVisibleAfterRenderTime() {
+        val overlayEnterTransitionStartDelay: Long = 2000
+        val overlayTag = "overlay"
+        val backgroundTag = "background"
+        rule.setContent {
+            CarouselItem(
+                overlayEnterTransitionStartDelayMillis = overlayEnterTransitionStartDelay,
+                background = {
+                    Box(
+                        Modifier
+                            .testTag(backgroundTag)
+                            .size(200.dp)
+                            .background(Color.Blue)) }) {
+                Box(
+                    Modifier
+                        .testTag(overlayTag)
+                        .size(50.dp)
+                        .background(Color.Red))
+            }
+        }
+
+        // only background is visible
+        rule.onNodeWithTag(backgroundTag).assertExists()
+        rule.onNodeWithTag(overlayTag).assertDoesNotExist()
+
+        // advance clock by `overlayEnterTransitionStartDelay`
+        rule.mainClock.advanceTimeBy(overlayEnterTransitionStartDelay)
+
+        rule.onNodeWithTag(backgroundTag).assertExists()
+        rule.onNodeWithTag(overlayTag).assertExists()
+    }
+
+    @OptIn(ExperimentalTvMaterial3Api::class)
+    @Test
+    fun carouselItem_parentContainerGainsFocused_onBackPress() {
+        rule.setContent {
+            Box(modifier = Modifier
+                .testTag("box-container")
+                .fillMaxSize()
+                .focusable()) {
+                CarouselItem(
+                    overlayEnterTransitionStartDelayMillis = 0,
+                    modifier = Modifier.testTag("carousel-item"),
+                    background = { Box(Modifier.size(300.dp).background(Color.Cyan)) }
+                ) {
+                    SampleButton()
+                }
+            }
+        }
+
+        // Request focus for Carousel Item on start
+        rule.onNodeWithTag("carousel-item")
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+        rule.waitForIdle()
+
+        // Check if overlay button in carousel item is focused
+        rule.onNodeWithTag("sample-button").assertIsFocused()
+
+        // Trigger back press
+        performKeyPress(NativeKeyEvent.KEYCODE_BACK)
+        rule.waitForIdle()
+
+        // Check if carousel item loses focus and parent container gains focus
+        rule.onNodeWithTag("box-container").assertIsFocused()
+    }
+
+    @Composable
+    private fun SampleButton(text: String = "sample-button") {
+        var isFocused by remember { mutableStateOf(false) }
+        BasicText(
+            text = text,
+            modifier = Modifier.testTag(text)
+                .size(100.dp, 20.dp)
+                .background(Color.Yellow)
+                .onFocusChanged { isFocused = it.isFocused }
+                .border(2.dp, if (isFocused) Color.Green else Color.Transparent)
+                .focusable()
+        )
+    }
+
+    private fun performKeyPress(keyCode: Int, count: Int = 1) {
+        repeat(count) {
+            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keyCode)
+        }
+    }
+}
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material/carousel/CarouselTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselTest.kt
similarity index 88%
rename from tv/tv-material/src/androidTest/java/androidx/tv/material/carousel/CarouselTest.kt
rename to tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselTest.kt
index a89cb06..2b0855f 100644
--- a/tv/tv-material/src/androidTest/java/androidx/tv/material/carousel/CarouselTest.kt
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.tv.material.carousel
+package androidx.tv.material
 
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
@@ -61,7 +61,13 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.platform.app.InstrumentationRegistry
-import androidx.tv.material.ExperimentalTvMaterialApi
+import androidx.tv.material3.Carousel
+import androidx.tv.material3.CarouselDefaults
+import androidx.tv.material3.CarouselItem
+import androidx.tv.material3.CarouselItemDefaults
+import androidx.tv.material3.CarouselState
+import androidx.tv.material3.ExperimentalTvMaterial3Api
+import androidx.tv.material3.ScrollPauseHandle
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
@@ -70,7 +76,7 @@
 private const val animationTime = 900L
 private const val overlayRenderWaitTime = 1500L
 
-@OptIn(ExperimentalTvMaterialApi::class)
+@OptIn(ExperimentalTvMaterial3Api::class)
 class CarouselTest {
     @get:Rule
     val rule = createComposeRule()
@@ -326,6 +332,78 @@
     }
 
     @Test
+    fun carousel_parentContainerGainsFocus_onBackPress() {
+        rule.setContent {
+            Box(modifier = Modifier
+                .testTag("box-container")
+                .fillMaxSize()
+                .focusable()) {
+                SampleCarousel { index ->
+                    SampleButton("Button-${index + 1}")
+                }
+            }
+        }
+
+        // Request focus for Carousel on start
+        rule.mainClock.autoAdvance = false
+        rule.onNodeWithTag("pager")
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+
+        // Trigger recomposition after requesting focus
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle()
+
+        // Check if the overlay button is focused
+        rule.onNodeWithText("Button-1").assertIsFocused()
+
+        // Trigger back press event to exit focus
+        performKeyPress(NativeKeyEvent.KEYCODE_BACK)
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle()
+
+        // Check if carousel loses focus and parent container gains focus
+        rule.onNodeWithText("Button-1").assertIsNotFocused()
+        rule.onNodeWithTag("box-container").assertIsFocused()
+    }
+
+    @Test
+    fun carousel_withCarouselItem_parentContainerGainsFocus_onBackPress() {
+        rule.setContent {
+            Box(modifier = Modifier
+                .testTag("box-container")
+                .fillMaxSize()
+                .focusable()) {
+                SampleCarousel {
+                    SampleCarouselSlide(index = it)
+                }
+            }
+        }
+
+        // Request focus for Carousel on start
+        rule.mainClock.autoAdvance = false
+        rule.onNodeWithTag("pager")
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+
+        // Trigger recomposition after requesting focus and advance time to finish animations
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle()
+        rule.mainClock.advanceTimeBy(animationTime + overlayRenderWaitTime, false)
+        rule.waitForIdle()
+
+        // Check if the overlay button is focused
+        rule.onNodeWithText("Play 0").assertIsFocused()
+
+        // Trigger back press event to exit focus
+        performKeyPress(NativeKeyEvent.KEYCODE_BACK)
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle()
+
+        // Check if carousel loses focus and parent container gains focus
+        rule.onNodeWithText("Play 0").assertIsNotFocused()
+        rule.onNodeWithTag("box-container").assertIsFocused()
+    }
+
+    @Test
     fun carousel_scrollToRegainFocus_checkBringIntoView() {
         val focusRequester = FocusRequester()
         rule.setContent {
@@ -589,7 +667,7 @@
     }
 }
 
-@OptIn(ExperimentalTvMaterialApi::class)
+@OptIn(ExperimentalTvMaterial3Api::class)
 @Composable
 private fun SampleCarousel(
     carouselState: CarouselState = remember { CarouselState() },
@@ -620,7 +698,7 @@
     )
 }
 
-@OptIn(ExperimentalTvMaterialApi::class)
+@OptIn(ExperimentalTvMaterial3Api::class)
 @Composable
 private fun SampleCarouselSlide(
     index: Int,
@@ -671,7 +749,7 @@
 }
 
 private fun performKeyPress(keyCode: Int, count: Int = 1) {
-    for (i in 1..count) {
+    repeat(count) {
         InstrumentationRegistry
             .getInstrumentation()
             .sendKeyDownUpSync(keyCode)
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material/immersivelist/ImmersiveListTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/ImmersiveListTest.kt
similarity index 85%
rename from tv/tv-material/src/androidTest/java/androidx/tv/material/immersivelist/ImmersiveListTest.kt
rename to tv/tv-material/src/androidTest/java/androidx/tv/material3/ImmersiveListTest.kt
index d128958..cf85887 100644
--- a/tv/tv-material/src/androidTest/java/androidx/tv/material/immersivelist/ImmersiveListTest.kt
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/ImmersiveListTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * 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.
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-package androidx.tv.material.immersivelist
+package androidx.tv.material3
 
 import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Box
@@ -26,25 +27,30 @@
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.input.key.NativeKeyEvent
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsFocused
 import androidx.compose.ui.test.getUnclippedBoundsInRoot
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performSemanticsAction
 import androidx.compose.ui.unit.dp
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.tv.foundation.lazy.list.TvLazyColumn
 import androidx.tv.foundation.lazy.list.TvLazyRow
-import androidx.tv.material.ExperimentalTvMaterialApi
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
@@ -53,43 +59,46 @@
     @get:Rule
     val rule = createComposeRule()
 
-    @OptIn(ExperimentalTvMaterialApi::class, ExperimentalAnimationApi::class)
+    @OptIn(ExperimentalTvMaterial3Api::class, ExperimentalAnimationApi::class)
     @Test
     fun immersiveList_scroll_backgroundChanges() {
-        val firstCard = FocusRequester()
-        val secondCard = FocusRequester()
-
         rule.setContent {
             ImmersiveList(
                 background = { index, _ ->
                     AnimatedContent(targetState = index) {
                         Box(
-                            Modifier
+                            modifier = Modifier
                                 .testTag("background-$it")
-                                .size(200.dp)) {
+                                .size(200.dp)
+                        ) {
                             BasicText("background-$it")
                         }
                     }
                 }) {
                     TvLazyRow {
                         items(3) { index ->
-                            var modifier = Modifier
-                                .testTag("card-$index")
-                                .size(100.dp)
-                            when (index) {
-                                0 -> modifier = modifier
-                                    .focusRequester(firstCard)
-                                1 -> modifier = modifier
-                                    .focusRequester(secondCard)
-                            }
+                            var isFocused by remember { mutableStateOf(false) }
 
-                            Box(modifier.focusableItem(index)) { BasicText("card-$index") }
+                            Box(
+                                modifier = Modifier
+                                    .size(100.dp)
+                                    .testTag("card-$index")
+                                    .background(if (isFocused) Color.Red else Color.Transparent)
+                                    .size(100.dp)
+                                    .onFocusChanged { isFocused = it.isFocused }
+                                    .immersiveListItem(index)
+                                    .focusable(true)
+                            ) {
+                                BasicText("card-$index")
+                            }
                         }
                     }
             }
         }
 
-        rule.runOnIdle { firstCard.requestFocus() }
+        rule.waitForIdle()
+        rule.onNodeWithTag("card-0").performSemanticsAction(SemanticsActions.RequestFocus)
+        rule.waitForIdle()
 
         rule.onNodeWithTag("card-0").assertIsFocused()
         rule.onNodeWithTag("background-0").assertIsDisplayed()
@@ -247,7 +256,7 @@
         rule.runOnIdle { focusRequester.requestFocus() }
     }
 
-    @OptIn(ExperimentalTvMaterialApi::class, ExperimentalAnimationApi::class)
+    @OptIn(ExperimentalTvMaterial3Api::class, ExperimentalAnimationApi::class)
     @Composable
     private fun TestImmersiveList(focusRequesterList: List<FocusRequester>) {
         val frList = remember { focusRequesterList }
@@ -275,7 +284,12 @@
                     for (item in frList) {
                         modifier = modifier.focusRequester(frList[index])
                     }
-                    Box(modifier.focusableItem(index)) { BasicText("list-card-$index") }
+                    Box(
+                        modifier
+                            .immersiveListItem(index)
+                            .focusable(true)) {
+                        BasicText("list-card-$index")
+                    }
                 }
             }
         }
@@ -285,4 +299,4 @@
         for (index in 0 until numberOfPresses)
             InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keyCode)
     }
-}
\ No newline at end of file
+}
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material/TabRowTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/TabRowTest.kt
similarity index 99%
rename from tv/tv-material/src/androidTest/java/androidx/tv/material/TabRowTest.kt
rename to tv/tv-material/src/androidTest/java/androidx/tv/material3/TabRowTest.kt
index 7698c60..0a835b2 100644
--- a/tv/tv-material/src/androidTest/java/androidx/tv/material/TabRowTest.kt
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/TabRowTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.tv.material
+package androidx.tv.material3
 
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/TextTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/TextTest.kt
new file mode 100644
index 0000000..5293277
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/TextTest.kt
@@ -0,0 +1,245 @@
+/*
+ * 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.tv.material3
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performSemanticsAction
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.TextUnit
+import androidx.compose.ui.unit.em
+import androidx.compose.ui.unit.sp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class TextTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val ExpectedTextStyle = TextStyle(
+        color = Color.Blue,
+        textAlign = TextAlign.End,
+        fontSize = 32.sp,
+        fontStyle = FontStyle.Italic,
+        letterSpacing = 0.3.em
+    )
+
+    private val TestText = "TestText"
+
+    @Test
+    fun inheritsThemeTextStyle() {
+        var textColor: Color? = null
+        var textAlign: TextAlign? = null
+        var fontSize: TextUnit? = null
+        var fontStyle: FontStyle? = null
+        var letterSpacing: TextUnit? = null
+        rule.setContent {
+            ProvideTextStyle(ExpectedTextStyle) {
+                Box(Modifier.background(Color.White)) {
+                    Text(
+                        TestText,
+                        onTextLayout = {
+                            textColor = it.layoutInput.style.color
+                            textAlign = it.layoutInput.style.textAlign
+                            fontSize = it.layoutInput.style.fontSize
+                            fontStyle = it.layoutInput.style.fontStyle
+                            letterSpacing = it.layoutInput.style.letterSpacing
+                        }
+                    )
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(textColor).isEqualTo(ExpectedTextStyle.color)
+            Truth.assertThat(textAlign).isEqualTo(ExpectedTextStyle.textAlign)
+            Truth.assertThat(fontSize).isEqualTo(ExpectedTextStyle.fontSize)
+            Truth.assertThat(fontStyle).isEqualTo(ExpectedTextStyle.fontStyle)
+            Truth.assertThat(letterSpacing).isEqualTo(ExpectedTextStyle.letterSpacing)
+        }
+    }
+
+    @Test
+    fun settingCustomTextStyle() {
+        var textColor: Color? = null
+        var textAlign: TextAlign? = null
+        var fontSize: TextUnit? = null
+        var fontStyle: FontStyle? = null
+        var letterSpacing: TextUnit? = null
+        val testStyle = TextStyle(
+            color = Color.Green,
+            textAlign = TextAlign.Center,
+            fontSize = 16.sp,
+            fontStyle = FontStyle.Normal,
+            letterSpacing = 0.6.em
+        )
+        rule.setContent {
+            ProvideTextStyle(ExpectedTextStyle) {
+                Box(Modifier.background(Color.White)) {
+                    Text(
+                        TestText,
+                        style = testStyle,
+                        onTextLayout = {
+                            textColor = it.layoutInput.style.color
+                            textAlign = it.layoutInput.style.textAlign
+                            fontSize = it.layoutInput.style.fontSize
+                            fontStyle = it.layoutInput.style.fontStyle
+                            letterSpacing = it.layoutInput.style.letterSpacing
+                        }
+                    )
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(textColor).isEqualTo(testStyle.color)
+            Truth.assertThat(textAlign).isEqualTo(testStyle.textAlign)
+            Truth.assertThat(fontSize).isEqualTo(testStyle.fontSize)
+            Truth.assertThat(fontStyle).isEqualTo(testStyle.fontStyle)
+            Truth.assertThat(letterSpacing).isEqualTo(testStyle.letterSpacing)
+        }
+    }
+
+    @Test
+    fun settingParametersExplicitly() {
+        var textColor: Color? = null
+        var textAlign: TextAlign? = null
+        var fontSize: TextUnit? = null
+        var fontStyle: FontStyle? = null
+        var letterSpacing: TextUnit? = null
+        val expectedColor = Color.Green
+        val expectedTextAlign = TextAlign.Center
+        val expectedFontSize = 16.sp
+        val expectedFontStyle = FontStyle.Normal
+        val expectedLetterSpacing = 0.6.em
+
+        rule.setContent {
+            ProvideTextStyle(ExpectedTextStyle) {
+                Box(Modifier.background(Color.White)) {
+                    Text(
+                        TestText,
+                        color = expectedColor,
+                        textAlign = expectedTextAlign,
+                        fontSize = expectedFontSize,
+                        fontStyle = expectedFontStyle,
+                        letterSpacing = expectedLetterSpacing,
+                        onTextLayout = {
+                            textColor = it.layoutInput.style.color
+                            textAlign = it.layoutInput.style.textAlign
+                            fontSize = it.layoutInput.style.fontSize
+                            fontStyle = it.layoutInput.style.fontStyle
+                            letterSpacing = it.layoutInput.style.letterSpacing
+                        }
+                    )
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            // explicit parameters should override values from the style.
+            Truth.assertThat(textColor).isEqualTo(expectedColor)
+            Truth.assertThat(textAlign).isEqualTo(expectedTextAlign)
+            Truth.assertThat(fontSize).isEqualTo(expectedFontSize)
+            Truth.assertThat(fontStyle).isEqualTo(expectedFontStyle)
+            Truth.assertThat(letterSpacing).isEqualTo(expectedLetterSpacing)
+        }
+    }
+
+    // Not really an expected use-case, but we should ensure the behavior here is consistent.
+    @Test
+    fun settingColorAndTextStyle() {
+        var textColor: Color? = null
+        var textAlign: TextAlign? = null
+        var fontSize: TextUnit? = null
+        var fontStyle: FontStyle? = null
+        var letterSpacing: TextUnit? = null
+        val expectedColor = Color.Green
+        val expectedTextAlign = TextAlign.Center
+        val expectedFontSize = 16.sp
+        val expectedFontStyle = FontStyle.Normal
+        val expectedLetterSpacing = 0.6.em
+        rule.setContent {
+            ProvideTextStyle(ExpectedTextStyle) {
+                Box(Modifier.background(Color.White)) {
+                    // Set both color and style
+                    Text(
+                        TestText,
+                        color = expectedColor,
+                        textAlign = expectedTextAlign,
+                        fontSize = expectedFontSize,
+                        fontStyle = expectedFontStyle,
+                        letterSpacing = expectedLetterSpacing,
+                        style = ExpectedTextStyle,
+                        onTextLayout = {
+                            textColor = it.layoutInput.style.color
+                            textAlign = it.layoutInput.style.textAlign
+                            fontSize = it.layoutInput.style.fontSize
+                            fontStyle = it.layoutInput.style.fontStyle
+                            letterSpacing = it.layoutInput.style.letterSpacing
+                        }
+                    )
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            // explicit parameters should override values from the style.
+            Truth.assertThat(textColor).isEqualTo(expectedColor)
+            Truth.assertThat(textAlign).isEqualTo(expectedTextAlign)
+            Truth.assertThat(fontSize).isEqualTo(expectedFontSize)
+            Truth.assertThat(fontStyle).isEqualTo(expectedFontStyle)
+            Truth.assertThat(letterSpacing).isEqualTo(expectedLetterSpacing)
+        }
+    }
+
+    @Test
+    fun testSemantics() {
+        rule.setContent {
+            ProvideTextStyle(ExpectedTextStyle) {
+                Box(Modifier.background(Color.White)) {
+                    Text(
+                        TestText,
+                        modifier = Modifier.testTag("text")
+                    )
+                }
+            }
+        }
+
+        val textLayoutResults = mutableListOf<TextLayoutResult>()
+        rule.onNodeWithTag("text")
+            .assertTextEquals(TestText)
+            .performSemanticsAction(SemanticsActions.GetTextLayoutResult) { it(textLayoutResults) }
+        assert(textLayoutResults.size == 1) { "TextLayoutResult is null" }
+    }
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material/BringIntoViewIfChildrenAreFocused.kt b/tv/tv-material/src/main/java/androidx/tv/material3/BringIntoViewIfChildrenAreFocused.kt
similarity index 96%
rename from tv/tv-material/src/main/java/androidx/tv/material/BringIntoViewIfChildrenAreFocused.kt
rename to tv/tv-material/src/main/java/androidx/tv/material3/BringIntoViewIfChildrenAreFocused.kt
index 0c4e5a8..b973bb7 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material/BringIntoViewIfChildrenAreFocused.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/BringIntoViewIfChildrenAreFocused.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.tv.material
+package androidx.tv.material3
 
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.relocation.BringIntoViewResponder
diff --git a/tv/tv-material/src/main/java/androidx/tv/material/carousel/Carousel.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
similarity index 92%
rename from tv/tv-material/src/main/java/androidx/tv/material/carousel/Carousel.kt
rename to tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
index de7a1b3..d4b5887 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material/carousel/Carousel.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * 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.
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package androidx.tv.material.carousel
+package androidx.tv.material3
 
+import android.view.KeyEvent.KEYCODE_BACK
 import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.AnimatedVisibilityScope
 import androidx.compose.animation.EnterTransition
@@ -53,13 +54,16 @@
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
+import androidx.compose.ui.input.key.key
+import androidx.compose.ui.input.key.nativeKeyCode
+import androidx.compose.ui.input.key.onKeyEvent
+import androidx.compose.ui.input.key.type
 import androidx.compose.ui.platform.LocalFocusManager
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
-import androidx.tv.material.ExperimentalTvMaterialApi
-import androidx.tv.material.bringIntoViewIfChildrenAreFocused
 import java.lang.Math.floorMod
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.first
@@ -72,6 +76,7 @@
  * @sample androidx.tv.samples.SimpleCarousel
  * @sample androidx.tv.samples.CarouselIndicatorWithRectangleShape
  *
+ * @param modifier Modifier applied to the Carousel.
  * @param slideCount total number of slides present in the carousel.
  * @param carouselState state associated with this carousel.
  * @param timeToDisplaySlideMillis duration for which slide should be visible before moving to
@@ -84,7 +89,7 @@
 
 @Suppress("IllegalExperimentalApiUsage")
 @OptIn(ExperimentalComposeUiApi::class, ExperimentalAnimationApi::class)
-@ExperimentalTvMaterialApi
+@ExperimentalTvMaterial3Api
 @Composable
 fun Carousel(
     slideCount: Int,
@@ -122,6 +127,12 @@
     Box(modifier = modifier
         .bringIntoViewIfChildrenAreFocused()
         .focusRequester(carouselOuterBoxFocusRequester)
+        .onKeyEvent {
+            if (it.key.nativeKeyCode == KEYCODE_BACK && it.type == KeyDown) {
+                focusManager.moveFocus(FocusDirection.Exit)
+            }
+            false
+        }
         .onFocusChanged {
             focusState = it
             if (it.isFocused && isAutoScrollActive) {
@@ -155,7 +166,7 @@
     action.invoke()
 }
 
-@OptIn(ExperimentalTvMaterialApi::class)
+@OptIn(ExperimentalTvMaterial3Api::class)
 @Composable
 private fun AutoScrollSideEffect(
     timeToDisplaySlideMillis: Long,
@@ -187,7 +198,7 @@
 }
 
 @Suppress("IllegalExperimentalApiUsage")
-@OptIn(ExperimentalTvMaterialApi::class, ExperimentalComposeUiApi::class)
+@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalComposeUiApi::class)
 private fun Modifier.manualScrolling(
     carouselState: CarouselState,
     slideCount: Int,
@@ -233,7 +244,7 @@
         }
     }
 
-@OptIn(ExperimentalTvMaterialApi::class)
+@OptIn(ExperimentalTvMaterial3Api::class)
 @Composable
 private fun CarouselStateUpdater(carouselState: CarouselState, slideCount: Int) {
     LaunchedEffect(carouselState, slideCount) {
@@ -252,7 +263,7 @@
  * @param initialActiveSlideIndex the index of the first active slide
  */
 @Stable
-@ExperimentalTvMaterialApi
+@ExperimentalTvMaterial3Api
 class CarouselState(initialActiveSlideIndex: Int = 0) {
     internal var activePauseHandlesCount by mutableStateOf(0)
 
@@ -295,7 +306,7 @@
     }
 }
 
-@ExperimentalTvMaterialApi
+@ExperimentalTvMaterial3Api
 /**
  * Handle returned by [CarouselState.pauseAutoScroll] that can be used to resume auto-scroll.
  */
@@ -306,7 +317,7 @@
     fun resumeAutoScroll()
 }
 
-@OptIn(ExperimentalTvMaterialApi::class)
+@OptIn(ExperimentalTvMaterial3Api::class)
 internal object NoOpScrollPauseHandle : ScrollPauseHandle {
     /**
      * Resumes the auto-scroll behaviour if there are no other active [ScrollPauseHandle]s.
@@ -314,7 +325,7 @@
     override fun resumeAutoScroll() {}
 }
 
-@OptIn(ExperimentalTvMaterialApi::class)
+@OptIn(ExperimentalTvMaterial3Api::class)
 internal class ScrollPauseHandleImpl(private val carouselState: CarouselState) : ScrollPauseHandle {
     private var active by mutableStateOf(true)
     init {
@@ -331,7 +342,7 @@
     }
 }
 
-@ExperimentalTvMaterialApi
+@ExperimentalTvMaterial3Api
 object CarouselDefaults {
     /**
      * Default time for which the slide is visible to the user.
@@ -358,7 +369,7 @@
      * @param spacing spacing between the indicator dots
      * @param indicator indicator dot representing each slide in the carousel
      */
-    @ExperimentalTvMaterialApi
+    @ExperimentalTvMaterial3Api
     @Composable
     fun IndicatorRow(
         slideCount: Int,
diff --git a/tv/tv-material/src/main/java/androidx/tv/material/carousel/CarouselItem.kt b/tv/tv-material/src/main/java/androidx/tv/material3/CarouselItem.kt
similarity index 83%
rename from tv/tv-material/src/main/java/androidx/tv/material/carousel/CarouselItem.kt
rename to tv/tv-material/src/main/java/androidx/tv/material3/CarouselItem.kt
index c39c68b..0b10e6f 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material/carousel/CarouselItem.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/CarouselItem.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * 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.
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package androidx.tv.material.carousel
+package androidx.tv.material3
 
+import android.view.KeyEvent
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.EnterTransition
 import androidx.compose.animation.ExitTransition
@@ -37,8 +38,12 @@
 import androidx.compose.ui.focus.FocusDirection
 import androidx.compose.ui.focus.FocusState
 import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
+import androidx.compose.ui.input.key.key
+import androidx.compose.ui.input.key.nativeKeyCode
+import androidx.compose.ui.input.key.onKeyEvent
+import androidx.compose.ui.input.key.type
 import androidx.compose.ui.platform.LocalFocusManager
-import androidx.tv.material.ExperimentalTvMaterialApi
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.first
 
@@ -49,6 +54,7 @@
  * - an [overlay] layer that is rendered after a delay of
  *   [overlayEnterTransitionStartDelayMillis].
  *
+ * @param modifier modifier applied to the CarouselItem.
  * @param overlayEnterTransitionStartDelayMillis time between the rendering of the
  * background and the overlay.
  * @param overlayEnterTransition animation used to bring the overlay into view.
@@ -58,7 +64,7 @@
  */
 @Suppress("IllegalExperimentalApiUsage")
 @OptIn(ExperimentalComposeUiApi::class)
-@ExperimentalTvMaterialApi
+@ExperimentalTvMaterial3Api
 @Composable
 fun CarouselItem(
     background: @Composable () -> Unit,
@@ -72,6 +78,7 @@
     val overlayVisible = remember { MutableTransitionState(initialState = false) }
     var focusState: FocusState? by remember { mutableStateOf(null) }
     val focusManager = LocalFocusManager.current
+    var exitFocus by remember { mutableStateOf(false) }
 
     LaunchedEffect(overlayVisible) {
         overlayVisible.onAnimationCompletion {
@@ -81,9 +88,16 @@
     }
 
     Box(modifier = modifier
+        .onKeyEvent {
+            exitFocus = it.key.nativeKeyCode == KeyEvent.KEYCODE_BACK && it.type == KeyDown
+            false
+        }
         .onFocusChanged {
             focusState = it
-            if (it.isFocused && overlayVisible.isIdle && overlayVisible.currentState) {
+            if (it.isFocused && exitFocus) {
+                focusManager.moveFocus(FocusDirection.Exit)
+                exitFocus = false
+            } else if (it.isFocused && overlayVisible.isIdle && overlayVisible.currentState) {
                 focusManager.moveFocus(FocusDirection.Enter)
             }
         }
@@ -99,13 +113,7 @@
 
         AnimatedVisibility(
             modifier = Modifier
-                .align(Alignment.BottomStart)
-                .onFocusChanged {
-                    if (it.isFocused) {
-                        focusManager.moveFocus(FocusDirection.Enter)
-                    }
-                }
-                .focusable(),
+                .align(Alignment.BottomStart),
             visibleState = overlayVisible,
             enter = overlayEnterTransition,
             exit = overlayExitTransition
@@ -122,7 +130,7 @@
     action.invoke()
 }
 
-@ExperimentalTvMaterialApi
+@ExperimentalTvMaterial3Api
 object CarouselItemDefaults {
     /**
      * Default delay between the background being rendered and the overlay being rendered.
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/ColorScheme.kt b/tv/tv-material/src/main/java/androidx/tv/material3/ColorScheme.kt
new file mode 100644
index 0000000..257754a
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/ColorScheme.kt
@@ -0,0 +1,625 @@
+/*
+ * 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.tv.material3
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.runtime.structuralEqualityPolicy
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.compositeOver
+import androidx.compose.ui.graphics.takeOrElse
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.tv.material3.tokens.ColorDarkTokens
+import androidx.tv.material3.tokens.ColorLightTokens
+import androidx.tv.material3.tokens.ColorSchemeKeyTokens
+import kotlin.math.ln
+
+/**
+ * Returns a light Material color scheme.
+ */
+@ExperimentalTvMaterial3Api
+fun lightColorScheme(
+    primary: Color = ColorLightTokens.Primary,
+    onPrimary: Color = ColorLightTokens.OnPrimary,
+    primaryContainer: Color = ColorLightTokens.PrimaryContainer,
+    onPrimaryContainer: Color = ColorLightTokens.OnPrimaryContainer,
+    inversePrimary: Color = ColorLightTokens.InversePrimary,
+    secondary: Color = ColorLightTokens.Secondary,
+    onSecondary: Color = ColorLightTokens.OnSecondary,
+    secondaryContainer: Color = ColorLightTokens.SecondaryContainer,
+    onSecondaryContainer: Color = ColorLightTokens.OnSecondaryContainer,
+    tertiary: Color = ColorLightTokens.Tertiary,
+    onTertiary: Color = ColorLightTokens.OnTertiary,
+    tertiaryContainer: Color = ColorLightTokens.TertiaryContainer,
+    onTertiaryContainer: Color = ColorLightTokens.OnTertiaryContainer,
+    background: Color = ColorLightTokens.Background,
+    onBackground: Color = ColorLightTokens.OnBackground,
+    surface: Color = ColorLightTokens.Surface,
+    onSurface: Color = ColorLightTokens.OnSurface,
+    surfaceVariant: Color = ColorLightTokens.SurfaceVariant,
+    onSurfaceVariant: Color = ColorLightTokens.OnSurfaceVariant,
+    surfaceTint: Color = primary,
+    inverseSurface: Color = ColorLightTokens.InverseSurface,
+    inverseOnSurface: Color = ColorLightTokens.InverseOnSurface,
+    error: Color = ColorLightTokens.Error,
+    onError: Color = ColorLightTokens.OnError,
+    errorContainer: Color = ColorLightTokens.ErrorContainer,
+    onErrorContainer: Color = ColorLightTokens.OnErrorContainer,
+    outline: Color = ColorLightTokens.Outline,
+    outlineVariant: Color = ColorLightTokens.OutlineVariant,
+    scrim: Color = ColorLightTokens.Scrim
+): ColorScheme =
+    ColorScheme(
+        primary = primary,
+        onPrimary = onPrimary,
+        primaryContainer = primaryContainer,
+        onPrimaryContainer = onPrimaryContainer,
+        inversePrimary = inversePrimary,
+        secondary = secondary,
+        onSecondary = onSecondary,
+        secondaryContainer = secondaryContainer,
+        onSecondaryContainer = onSecondaryContainer,
+        tertiary = tertiary,
+        onTertiary = onTertiary,
+        tertiaryContainer = tertiaryContainer,
+        onTertiaryContainer = onTertiaryContainer,
+        background = background,
+        onBackground = onBackground,
+        surface = surface,
+        onSurface = onSurface,
+        surfaceVariant = surfaceVariant,
+        onSurfaceVariant = onSurfaceVariant,
+        surfaceTint = surfaceTint,
+        inverseSurface = inverseSurface,
+        inverseOnSurface = inverseOnSurface,
+        error = error,
+        onError = onError,
+        errorContainer = errorContainer,
+        onErrorContainer = onErrorContainer,
+        outline = outline,
+        outlineVariant = outlineVariant,
+        scrim = scrim
+    )
+
+/**
+ * A color scheme holds all the named color parameters for a [MaterialTheme].
+ *
+ * Color schemes are designed to be harmonious, ensure accessible text, and distinguish UI
+ * elements and surfaces from one another. There are two built-in baseline schemes,
+ * [lightColorScheme] and a [darkColorScheme], that can be used as-is or customized.
+ *
+ * The Material color system and custom schemes provide default values for color as a starting point
+ * for customization.
+ *
+ * To learn more about colors, see [Material Design colors](https://m3.material.io/styles/color/overview).
+ *
+ * @property primary The primary color is the color displayed most frequently across your app’s
+ * screens and components.
+ * @property onPrimary Color used for text and icons displayed on top of the primary color.
+ * @property primaryContainer The preferred tonal color of containers.
+ * @property onPrimaryContainer The color (and state variants) that should be used for content on
+ * top of [primaryContainer].
+ * @property inversePrimary Color to be used as a "primary" color in places where the inverse color
+ * scheme is needed, such as the button on a SnackBar.
+ * @property secondary The secondary color provides more ways to accent and distinguish your
+ * product. Secondary colors are best for:
+ * - Floating action buttons
+ * - Selection controls, like checkboxes and radio buttons
+ * - Highlighting selected text
+ * - Links and headlines
+ * @property onSecondary Color used for text and icons displayed on top of the secondary color.
+ * @property secondaryContainer A tonal color to be used in containers.
+ * @property onSecondaryContainer The color (and state variants) that should be used for content on
+ * top of [secondaryContainer].
+ * @property tertiary The tertiary color that can be used to balance primary and secondary
+ * colors, or bring heightened attention to an element such as an input field.
+ * @property onTertiary Color used for text and icons displayed on top of the tertiary color.
+ * @property tertiaryContainer A tonal color to be used in containers.
+ * @property onTertiaryContainer The color (and state variants) that should be used for content on
+ * top of [tertiaryContainer].
+ * @property background The background color that appears behind scrollable content.
+ * @property onBackground Color used for text and icons displayed on top of the background color.
+ * @property surface The surface color that affect surfaces of components, such as cards, sheets,
+ * and menus.
+ * @property onSurface Color used for text and icons displayed on top of the surface color.
+ * @property surfaceVariant Another option for a color with similar uses of [surface].
+ * @property onSurfaceVariant The color (and state variants) that can be used for content on top of
+ * [surface].
+ * @property surfaceTint This color will be used by components that apply tonal elevation and is
+ * applied on top of [surface]. The higher the elevation the more this color is used.
+ * @property inverseSurface A color that contrasts sharply with [surface]. Useful for surfaces that
+ * sit on top of other surfaces with [surface] color.
+ * @property inverseOnSurface A color that contrasts well with [inverseSurface]. Useful for content
+ * that sits on top of containers that are [inverseSurface].
+ * @property error The error color is used to indicate errors in components, such as invalid text in
+ * a text field.
+ * @property onError Color used for text and icons displayed on top of the error color.
+ * @property errorContainer The preferred tonal color of error containers.
+ * @property onErrorContainer The color (and state variants) that should be used for content on
+ * top of [errorContainer].
+ * @property outline Subtle color used for boundaries. Outline color role adds contrast for
+ * accessibility purposes.
+ * @property outlineVariant Utility color used for boundaries for decorative elements when strong
+ * contrast is not required.
+ * @property scrim Color of a scrim that obscures content.
+ */
+@ExperimentalTvMaterial3Api
+@Stable
+class ColorScheme(
+    primary: Color,
+    onPrimary: Color,
+    primaryContainer: Color,
+    onPrimaryContainer: Color,
+    inversePrimary: Color,
+    secondary: Color,
+    onSecondary: Color,
+    secondaryContainer: Color,
+    onSecondaryContainer: Color,
+    tertiary: Color,
+    onTertiary: Color,
+    tertiaryContainer: Color,
+    onTertiaryContainer: Color,
+    background: Color,
+    onBackground: Color,
+    surface: Color,
+    onSurface: Color,
+    surfaceVariant: Color,
+    onSurfaceVariant: Color,
+    surfaceTint: Color,
+    inverseSurface: Color,
+    inverseOnSurface: Color,
+    error: Color,
+    onError: Color,
+    errorContainer: Color,
+    onErrorContainer: Color,
+    outline: Color,
+    outlineVariant: Color,
+    scrim: Color
+) {
+    var primary by mutableStateOf(primary, structuralEqualityPolicy())
+        internal set
+    var onPrimary by mutableStateOf(onPrimary, structuralEqualityPolicy())
+        internal set
+    var primaryContainer by mutableStateOf(primaryContainer, structuralEqualityPolicy())
+        internal set
+    var onPrimaryContainer by mutableStateOf(onPrimaryContainer, structuralEqualityPolicy())
+        internal set
+    var inversePrimary by mutableStateOf(inversePrimary, structuralEqualityPolicy())
+        internal set
+    var secondary by mutableStateOf(secondary, structuralEqualityPolicy())
+        internal set
+    var onSecondary by mutableStateOf(onSecondary, structuralEqualityPolicy())
+        internal set
+    var secondaryContainer by mutableStateOf(secondaryContainer, structuralEqualityPolicy())
+        internal set
+    var onSecondaryContainer by mutableStateOf(onSecondaryContainer, structuralEqualityPolicy())
+        internal set
+    var tertiary by mutableStateOf(tertiary, structuralEqualityPolicy())
+        internal set
+    var onTertiary by mutableStateOf(onTertiary, structuralEqualityPolicy())
+        internal set
+    var tertiaryContainer by mutableStateOf(tertiaryContainer, structuralEqualityPolicy())
+        internal set
+    var onTertiaryContainer by mutableStateOf(onTertiaryContainer, structuralEqualityPolicy())
+        internal set
+    var background by mutableStateOf(background, structuralEqualityPolicy())
+        internal set
+    var onBackground by mutableStateOf(onBackground, structuralEqualityPolicy())
+        internal set
+    var surface by mutableStateOf(surface, structuralEqualityPolicy())
+        internal set
+    var onSurface by mutableStateOf(onSurface, structuralEqualityPolicy())
+        internal set
+    var surfaceVariant by mutableStateOf(surfaceVariant, structuralEqualityPolicy())
+        internal set
+    var onSurfaceVariant by mutableStateOf(onSurfaceVariant, structuralEqualityPolicy())
+        internal set
+    var surfaceTint by mutableStateOf(surfaceTint, structuralEqualityPolicy())
+        internal set
+    var inverseSurface by mutableStateOf(inverseSurface, structuralEqualityPolicy())
+        internal set
+    var inverseOnSurface by mutableStateOf(inverseOnSurface, structuralEqualityPolicy())
+        internal set
+    var error by mutableStateOf(error, structuralEqualityPolicy())
+        internal set
+    var onError by mutableStateOf(onError, structuralEqualityPolicy())
+        internal set
+    var errorContainer by mutableStateOf(errorContainer, structuralEqualityPolicy())
+        internal set
+    var onErrorContainer by mutableStateOf(onErrorContainer, structuralEqualityPolicy())
+        internal set
+    var outline by mutableStateOf(outline, structuralEqualityPolicy())
+        internal set
+    var outlineVariant by mutableStateOf(outlineVariant, structuralEqualityPolicy())
+        internal set
+    var scrim by mutableStateOf(scrim, structuralEqualityPolicy())
+        internal set
+
+    /** Returns a copy of this ColorScheme, optionally overriding some of the values. */
+    fun copy(
+        primary: Color = this.primary,
+        onPrimary: Color = this.onPrimary,
+        primaryContainer: Color = this.primaryContainer,
+        onPrimaryContainer: Color = this.onPrimaryContainer,
+        inversePrimary: Color = this.inversePrimary,
+        secondary: Color = this.secondary,
+        onSecondary: Color = this.onSecondary,
+        secondaryContainer: Color = this.secondaryContainer,
+        onSecondaryContainer: Color = this.onSecondaryContainer,
+        tertiary: Color = this.tertiary,
+        onTertiary: Color = this.onTertiary,
+        tertiaryContainer: Color = this.tertiaryContainer,
+        onTertiaryContainer: Color = this.onTertiaryContainer,
+        background: Color = this.background,
+        onBackground: Color = this.onBackground,
+        surface: Color = this.surface,
+        onSurface: Color = this.onSurface,
+        surfaceVariant: Color = this.surfaceVariant,
+        onSurfaceVariant: Color = this.onSurfaceVariant,
+        surfaceTint: Color = this.surfaceTint,
+        inverseSurface: Color = this.inverseSurface,
+        inverseOnSurface: Color = this.inverseOnSurface,
+        error: Color = this.error,
+        onError: Color = this.onError,
+        errorContainer: Color = this.errorContainer,
+        onErrorContainer: Color = this.onErrorContainer,
+        outline: Color = this.outline,
+        outlineVariant: Color = this.outlineVariant,
+        scrim: Color = this.scrim
+    ): ColorScheme =
+        ColorScheme(
+            primary = primary,
+            onPrimary = onPrimary,
+            primaryContainer = primaryContainer,
+            onPrimaryContainer = onPrimaryContainer,
+            inversePrimary = inversePrimary,
+            secondary = secondary,
+            onSecondary = onSecondary,
+            secondaryContainer = secondaryContainer,
+            onSecondaryContainer = onSecondaryContainer,
+            tertiary = tertiary,
+            onTertiary = onTertiary,
+            tertiaryContainer = tertiaryContainer,
+            onTertiaryContainer = onTertiaryContainer,
+            background = background,
+            onBackground = onBackground,
+            surface = surface,
+            onSurface = onSurface,
+            surfaceVariant = surfaceVariant,
+            onSurfaceVariant = onSurfaceVariant,
+            surfaceTint = surfaceTint,
+            inverseSurface = inverseSurface,
+            inverseOnSurface = inverseOnSurface,
+            error = error,
+            onError = onError,
+            errorContainer = errorContainer,
+            onErrorContainer = onErrorContainer,
+            outline = outline,
+            outlineVariant = outlineVariant,
+            scrim = scrim
+        )
+
+    override fun toString(): String {
+        return "ColorScheme(" +
+            "primary=$primary" +
+            "onPrimary=$onPrimary" +
+            "primaryContainer=$primaryContainer" +
+            "onPrimaryContainer=$onPrimaryContainer" +
+            "inversePrimary=$inversePrimary" +
+            "secondary=$secondary" +
+            "onSecondary=$onSecondary" +
+            "secondaryContainer=$secondaryContainer" +
+            "onSecondaryContainer=$onSecondaryContainer" +
+            "tertiary=$tertiary" +
+            "onTertiary=$onTertiary" +
+            "tertiaryContainer=$tertiaryContainer" +
+            "onTertiaryContainer=$onTertiaryContainer" +
+            "background=$background" +
+            "onBackground=$onBackground" +
+            "surface=$surface" +
+            "onSurface=$onSurface" +
+            "surfaceVariant=$surfaceVariant" +
+            "onSurfaceVariant=$onSurfaceVariant" +
+            "surfaceTint=$surfaceTint" +
+            "inverseSurface=$inverseSurface" +
+            "inverseOnSurface=$inverseOnSurface" +
+            "error=$error" +
+            "onError=$onError" +
+            "errorContainer=$errorContainer" +
+            "onErrorContainer=$onErrorContainer" +
+            "outline=$outline" +
+            "outlineVariant=$outlineVariant" +
+            "scrim=$scrim" +
+            ")"
+    }
+}
+
+/**
+ * Returns a dark Material color scheme.
+ */
+@ExperimentalTvMaterial3Api
+fun darkColorScheme(
+    primary: Color = ColorDarkTokens.Primary,
+    onPrimary: Color = ColorDarkTokens.OnPrimary,
+    primaryContainer: Color = ColorDarkTokens.PrimaryContainer,
+    onPrimaryContainer: Color = ColorDarkTokens.OnPrimaryContainer,
+    inversePrimary: Color = ColorDarkTokens.InversePrimary,
+    secondary: Color = ColorDarkTokens.Secondary,
+    onSecondary: Color = ColorDarkTokens.OnSecondary,
+    secondaryContainer: Color = ColorDarkTokens.SecondaryContainer,
+    onSecondaryContainer: Color = ColorDarkTokens.OnSecondaryContainer,
+    tertiary: Color = ColorDarkTokens.Tertiary,
+    onTertiary: Color = ColorDarkTokens.OnTertiary,
+    tertiaryContainer: Color = ColorDarkTokens.TertiaryContainer,
+    onTertiaryContainer: Color = ColorDarkTokens.OnTertiaryContainer,
+    background: Color = ColorDarkTokens.Background,
+    onBackground: Color = ColorDarkTokens.OnBackground,
+    surface: Color = ColorDarkTokens.Surface,
+    onSurface: Color = ColorDarkTokens.OnSurface,
+    surfaceVariant: Color = ColorDarkTokens.SurfaceVariant,
+    onSurfaceVariant: Color = ColorDarkTokens.OnSurfaceVariant,
+    surfaceTint: Color = primary,
+    inverseSurface: Color = ColorDarkTokens.InverseSurface,
+    inverseOnSurface: Color = ColorDarkTokens.InverseOnSurface,
+    error: Color = ColorDarkTokens.Error,
+    onError: Color = ColorDarkTokens.OnError,
+    errorContainer: Color = ColorDarkTokens.ErrorContainer,
+    onErrorContainer: Color = ColorDarkTokens.OnErrorContainer,
+    outline: Color = ColorDarkTokens.Outline,
+    outlineVariant: Color = ColorDarkTokens.OutlineVariant,
+    scrim: Color = ColorDarkTokens.Scrim
+): ColorScheme =
+    ColorScheme(
+        primary = primary,
+        onPrimary = onPrimary,
+        primaryContainer = primaryContainer,
+        onPrimaryContainer = onPrimaryContainer,
+        inversePrimary = inversePrimary,
+        secondary = secondary,
+        onSecondary = onSecondary,
+        secondaryContainer = secondaryContainer,
+        onSecondaryContainer = onSecondaryContainer,
+        tertiary = tertiary,
+        onTertiary = onTertiary,
+        tertiaryContainer = tertiaryContainer,
+        onTertiaryContainer = onTertiaryContainer,
+        background = background,
+        onBackground = onBackground,
+        surface = surface,
+        onSurface = onSurface,
+        surfaceVariant = surfaceVariant,
+        onSurfaceVariant = onSurfaceVariant,
+        surfaceTint = surfaceTint,
+        inverseSurface = inverseSurface,
+        inverseOnSurface = inverseOnSurface,
+        error = error,
+        onError = onError,
+        errorContainer = errorContainer,
+        onErrorContainer = onErrorContainer,
+        outline = outline,
+        outlineVariant = outlineVariant,
+        scrim = scrim
+    )
+
+/**
+ * The Material color system contains pairs of colors that are typically used for the background and
+ * content color inside a component. For example, a Button typically uses `primary` for its
+ * background, and `onPrimary` for the color of its content (usually text or iconography).
+ *
+ * This function tries to match the provided [backgroundColor] to a 'background' color in this
+ * [ColorScheme], and then will return the corresponding color used for content. For example, when
+ * [backgroundColor] is [ColorScheme.primary], this will return [ColorScheme.onPrimary].
+ *
+ * If [backgroundColor] does not match a background color in the theme, this will return
+ * [Color.Unspecified].
+ *
+ * @return the matching content color for [backgroundColor]. If [backgroundColor] is not present in
+ * the theme's [ColorScheme], then returns [Color.Unspecified].
+ *
+ * @see contentColorFor
+ */
+@ExperimentalTvMaterial3Api
+fun ColorScheme.contentColorFor(backgroundColor: Color): Color =
+    when (backgroundColor) {
+        primary -> onPrimary
+        secondary -> onSecondary
+        tertiary -> onTertiary
+        background -> onBackground
+        error -> onError
+        surface -> onSurface
+        surfaceVariant -> onSurfaceVariant
+        primaryContainer -> onPrimaryContainer
+        secondaryContainer -> onSecondaryContainer
+        tertiaryContainer -> onTertiaryContainer
+        errorContainer -> onErrorContainer
+        inverseSurface -> inverseOnSurface
+        else -> Color.Unspecified
+    }
+
+/**
+ * The Material color system contains pairs of colors that are typically used for the background and
+ * content color inside a component. For example, a Button typically uses `primary` for its
+ * background, and `onPrimary` for the color of its content (usually text or iconography).
+ *
+ * This function tries to match the provided [backgroundColor] to a 'background' color in this
+ * [ColorScheme], and then will return the corresponding color used for content. For example, when
+ * [backgroundColor] is [ColorScheme.primary], this will return [ColorScheme.onPrimary].
+ *
+ * If [backgroundColor] does not match a background color in the theme, this will return the current
+ * value of [LocalContentColor] as a best-effort color.
+ *
+ * @return the matching content color for [backgroundColor]. If [backgroundColor] is not present in
+ * the theme's [ColorScheme], then returns the current value of [LocalContentColor].
+ *
+ * @see ColorScheme.contentColorFor
+ */
+@Composable
+@ReadOnlyComposable
+@ExperimentalTvMaterial3Api
+fun contentColorFor(backgroundColor: Color) =
+    MaterialTheme.colorScheme.contentColorFor(backgroundColor).takeOrElse {
+        LocalContentColor.current
+    }
+
+/**
+ * Returns the new background [Color] to use, representing the original background [color] with an
+ * overlay corresponding to [elevation] applied. The overlay will only be applied to
+ * [ColorScheme.surface].
+ */
+@OptIn(ExperimentalTvMaterial3Api::class)
+internal fun ColorScheme.applyTonalElevation(backgroundColor: Color, elevation: Dp): Color {
+    return if (backgroundColor == surface) {
+        surfaceColorAtElevation(elevation)
+    } else {
+        backgroundColor
+    }
+}
+
+/**
+ * Computes the surface tonal color at different elevation levels e.g. surface1 through surface5.
+ *
+ * @param elevation Elevation value used to compute alpha of the color overlay layer.
+ *
+ * @return the [ColorScheme.surface] color with an alpha of the [ColorScheme.surfaceTint] color
+ * overlaid on top of it.
+
+ */
+@ExperimentalTvMaterial3Api
+fun ColorScheme.surfaceColorAtElevation(
+    elevation: Dp
+): Color {
+    if (elevation == 0.dp) return surface
+    val alpha = ((4.5f * ln(elevation.value + 1)) + 2f) / 100f
+    return surfaceTint.copy(alpha = alpha).compositeOver(surface)
+}
+
+/**
+ * Updates the internal values of a given [ColorScheme] with values from the [other]
+ * [ColorScheme].
+ * This allows efficiently updating a subset of [ColorScheme], without recomposing every
+ * composable that consumes values from [LocalColorScheme].
+ *
+ * Because [ColorScheme] is very wide-reaching, and used by many expensive composables in the
+ * hierarchy, providing a new value to [LocalColorScheme] causes every composable consuming
+ * [LocalColorScheme] to recompose, which is prohibitively expensive in cases such as animating one
+ * color in the theme. Instead, [ColorScheme] is internally backed by [mutableStateOf], and this
+ * function mutates the internal state of [this] to match values in [other]. This means that any
+ * changes will mutate the internal state of [this], and only cause composables that are reading the
+ * specific changed value to recompose.
+ */
+@OptIn(ExperimentalTvMaterial3Api::class)
+internal fun ColorScheme.updateColorSchemeFrom(other: ColorScheme) {
+    primary = other.primary
+    onPrimary = other.onPrimary
+    primaryContainer = other.primaryContainer
+    onPrimaryContainer = other.onPrimaryContainer
+    inversePrimary = other.inversePrimary
+    secondary = other.secondary
+    onSecondary = other.onSecondary
+    secondaryContainer = other.secondaryContainer
+    onSecondaryContainer = other.onSecondaryContainer
+    tertiary = other.tertiary
+    onTertiary = other.onTertiary
+    tertiaryContainer = other.tertiaryContainer
+    onTertiaryContainer = other.onTertiaryContainer
+    background = other.background
+    onBackground = other.onBackground
+    surface = other.surface
+    onSurface = other.onSurface
+    surfaceVariant = other.surfaceVariant
+    onSurfaceVariant = other.onSurfaceVariant
+    surfaceTint = other.surfaceTint
+    inverseSurface = other.inverseSurface
+    inverseOnSurface = other.inverseOnSurface
+    error = other.error
+    onError = other.onError
+    errorContainer = other.errorContainer
+    onErrorContainer = other.onErrorContainer
+    outline = other.outline
+    outlineVariant = other.outlineVariant
+    scrim = other.scrim
+}
+
+/**
+ * Helper function for component color tokens. Here is an example on how to use component color
+ * tokens:
+ * ``MaterialTheme.colorScheme.fromToken(ExtendedFabBranded.BrandedContainerColor)``
+ */
+@OptIn(ExperimentalTvMaterial3Api::class)
+internal fun ColorScheme.fromToken(value: ColorSchemeKeyTokens): Color {
+    return when (value) {
+        ColorSchemeKeyTokens.Background -> background
+        ColorSchemeKeyTokens.Error -> error
+        ColorSchemeKeyTokens.ErrorContainer -> errorContainer
+        ColorSchemeKeyTokens.InverseOnSurface -> inverseOnSurface
+        ColorSchemeKeyTokens.InversePrimary -> inversePrimary
+        ColorSchemeKeyTokens.InverseSurface -> inverseSurface
+        ColorSchemeKeyTokens.OnBackground -> onBackground
+        ColorSchemeKeyTokens.OnError -> onError
+        ColorSchemeKeyTokens.OnErrorContainer -> onErrorContainer
+        ColorSchemeKeyTokens.OnPrimary -> onPrimary
+        ColorSchemeKeyTokens.OnPrimaryContainer -> onPrimaryContainer
+        ColorSchemeKeyTokens.OnSecondary -> onSecondary
+        ColorSchemeKeyTokens.OnSecondaryContainer -> onSecondaryContainer
+        ColorSchemeKeyTokens.OnSurface -> onSurface
+        ColorSchemeKeyTokens.OnSurfaceVariant -> onSurfaceVariant
+        ColorSchemeKeyTokens.SurfaceTint -> surfaceTint
+        ColorSchemeKeyTokens.OnTertiary -> onTertiary
+        ColorSchemeKeyTokens.OnTertiaryContainer -> onTertiaryContainer
+        ColorSchemeKeyTokens.Outline -> outline
+        ColorSchemeKeyTokens.OutlineVariant -> outlineVariant
+        ColorSchemeKeyTokens.Primary -> primary
+        ColorSchemeKeyTokens.PrimaryContainer -> primaryContainer
+        ColorSchemeKeyTokens.Scrim -> scrim
+        ColorSchemeKeyTokens.Secondary -> secondary
+        ColorSchemeKeyTokens.SecondaryContainer -> secondaryContainer
+        ColorSchemeKeyTokens.Surface -> surface
+        ColorSchemeKeyTokens.SurfaceVariant -> surfaceVariant
+        ColorSchemeKeyTokens.Tertiary -> tertiary
+        ColorSchemeKeyTokens.TertiaryContainer -> tertiaryContainer
+    }
+}
+
+/**
+ * CompositionLocal used to pass [ColorScheme] down the tree.
+ *
+ * Setting the value here is typically done as part of [MaterialTheme], which will automatically
+ * handle efficiently updating any changed colors without causing unnecessary recompositions, using
+ * [ColorScheme.updateColorSchemeFrom]. To retrieve the current value of this CompositionLocal, use
+ * [MaterialTheme.colorScheme].
+ */
+@OptIn(ExperimentalTvMaterial3Api::class)
+internal val LocalColorScheme = staticCompositionLocalOf { lightColorScheme() }
+
+/**
+ * A low level of alpha used to represent disabled components, such as text in a disabled Button.
+ */
+internal const val DisabledAlpha = 0.38f
+
+/** Converts a color token key to the local color scheme provided by the theme */
+@ReadOnlyComposable
+@Composable
+@OptIn(ExperimentalTvMaterial3Api::class)
+internal fun ColorSchemeKeyTokens.toColor(): Color {
+    return MaterialTheme.colorScheme.fromToken(this)
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material/ContentColor.kt b/tv/tv-material/src/main/java/androidx/tv/material3/ContentColor.kt
similarity index 94%
rename from tv/tv-material/src/main/java/androidx/tv/material/ContentColor.kt
rename to tv/tv-material/src/main/java/androidx/tv/material3/ContentColor.kt
index f1b11f5..261dc24 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material/ContentColor.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/ContentColor.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.tv.material
+package androidx.tv.material3
 
 import androidx.compose.runtime.compositionLocalOf
 import androidx.compose.ui.graphics.Color
diff --git a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt b/tv/tv-material/src/main/java/androidx/tv/material3/ExperimentalTvMaterial3Api.kt
similarity index 84%
rename from tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
rename to tv/tv-material/src/main/java/androidx/tv/material3/ExperimentalTvMaterial3Api.kt
index ebac64e..006ab10 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/ExperimentalTvMaterial3Api.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * 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.
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package androidx.tv.material
+package androidx.tv.material3
 
 @RequiresOptIn(
     "This tv-material API is experimental and likely to change or be removed in the future."
 )
 @Retention(AnnotationRetention.BINARY)
-annotation class ExperimentalTvMaterialApi
\ No newline at end of file
+annotation class ExperimentalTvMaterial3Api
\ No newline at end of file
diff --git a/tv/tv-material/src/main/java/androidx/tv/material/immersivelist/ImmersiveList.kt b/tv/tv-material/src/main/java/androidx/tv/material3/ImmersiveList.kt
similarity index 89%
rename from tv/tv-material/src/main/java/androidx/tv/material/immersivelist/ImmersiveList.kt
rename to tv/tv-material/src/main/java/androidx/tv/material3/ImmersiveList.kt
index e750f38..7a5631b 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material/immersivelist/ImmersiveList.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/ImmersiveList.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.tv.material.immersivelist
+package androidx.tv.material3
 
 import androidx.compose.animation.AnimatedContentScope
 import androidx.compose.animation.AnimatedVisibilityScope
@@ -26,7 +26,6 @@
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
 import androidx.compose.animation.with
-import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.runtime.Composable
@@ -41,8 +40,6 @@
 import androidx.compose.ui.focus.FocusDirection
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.platform.LocalFocusManager
-import androidx.tv.material.ExperimentalTvMaterialApi
-import androidx.tv.material.bringIntoViewIfChildrenAreFocused
 
 /**
  * Immersive List consists of a list with multiple items and a background that displays content
@@ -51,6 +48,8 @@
  * To display the background only when the list is in focus, use
  * [ImmersiveListBackgroundScope.AnimatedVisibility].
  *
+ * @sample androidx.tv.samples.SampleImmersiveList
+ *
  * @param background Composable defining the background to be displayed for a given item's
  * index. `listHasFocus` argument can be used to hide the background when the list is not in focus
  * @param modifier applied to Immersive List.
@@ -59,7 +58,7 @@
  */
 @Suppress("IllegalExperimentalApiUsage")
 @OptIn(ExperimentalComposeUiApi::class)
-@ExperimentalTvMaterialApi
+@ExperimentalTvMaterial3Api
 @Composable
 fun ImmersiveList(
     background:
@@ -85,7 +84,7 @@
     }
 }
 
-@ExperimentalTvMaterialApi
+@ExperimentalTvMaterial3Api
 object ImmersiveListDefaults {
     /**
      * Default transition used to bring the background content into view
@@ -99,7 +98,7 @@
 }
 
 @Immutable
-@ExperimentalTvMaterialApi
+@ExperimentalTvMaterial3Api
 public class ImmersiveListBackgroundScope internal constructor(boxScope: BoxScope) : BoxScope
 by boxScope {
 
@@ -113,6 +112,7 @@
      * @param modifier modifier for the Layout created to contain the [content]
      * @param enter EnterTransition(s) used for the appearing animation, fading in by default
      * @param exit ExitTransition(s) used for the disappearing animation, fading out by default
+     * @param label helps differentiate from other animations in Android Studio
      * @param content Content to appear or disappear based on the value of [visible]
      *
      * @link androidx.compose.animation.AnimatedVisibility
@@ -149,6 +149,8 @@
      * @param modifier modifier for the Layout created to contain the [content]
      * @param transitionSpec defines the EnterTransition(s) and ExitTransition(s) used to display
      * and remove the content, fading in and fading out by default
+     * @param contentAlignment specifies how the background content should be aligned in the
+     * container
      * @param content Content to appear or disappear based on the value of [targetState]
      *
      * @link androidx.compose.animation.AnimatedContent
@@ -179,16 +181,22 @@
 }
 
 @Immutable
-@ExperimentalTvMaterialApi
+@ExperimentalTvMaterial3Api
 public class ImmersiveListScope internal constructor(private val onFocused: (Int) -> Unit) {
     /**
      * Modifier to be added to each of the items of the list within ImmersiveList to inform the
-     * ImmersiveList of the index of the item in focus.
+     * ImmersiveList of the index of the item in focus
      *
-     * @param index index of the item within the list.
+     * > **NOTE**: This modifier needs to be paired with either the "focusable" or the "clickable"
+     * modifier for it to work
+     *
+     * @param index index of the item within the list
      */
-    fun Modifier.focusableItem(index: Int): Modifier {
-        return onFocusChanged { if (it.hasFocus || it.isFocused) { onFocused(index) } }
-            .focusable()
+    fun Modifier.immersiveListItem(index: Int): Modifier {
+        return onFocusChanged {
+            if (it.isFocused) {
+                onFocused(index)
+            }
+        }
     }
 }
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/MaterialTheme.kt b/tv/tv-material/src/main/java/androidx/tv/material3/MaterialTheme.kt
new file mode 100644
index 0000000..71ec11d
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/MaterialTheme.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.tv.material3
+
+import androidx.compose.foundation.text.selection.LocalTextSelectionColors
+import androidx.compose.foundation.text.selection.TextSelectionColors
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.remember
+
+/**
+ * Material Theming refers to the customization of your Material Design app to better reflect your
+ * product’s brand.
+ *
+ * Material components such as Button and Checkbox use values provided here when retrieving
+ * default values.
+ *
+ * All values may be set by providing this component with the [colorScheme][ColorScheme],
+ * [typography][Typography] attributes. Use this to configure the overall
+ * theme of elements within this MaterialTheme.
+ *
+ * Any values that are not set will inherit the current value from the theme, falling back to the
+ * defaults if there is no parent MaterialTheme. This allows using a MaterialTheme at the top
+ * of your application, and then separate MaterialTheme(s) for different screens / parts of your
+ * UI, overriding only the parts of the theme definition that need to change.
+ *
+ * @param colorScheme A complete definition of the Material Color theme for this hierarchy
+ * @param shapes A set of corner shapes to be used as this hierarchy's shape system
+ * @param typography A set of text styles to be used as this hierarchy's typography system
+ * @param content The composable content that will be displayed with this theme
+ */
+@ExperimentalTvMaterial3Api
+@Composable
+fun MaterialTheme(
+    colorScheme: ColorScheme = MaterialTheme.colorScheme,
+    shapes: Shapes = MaterialTheme.shapes,
+    typography: Typography = MaterialTheme.typography,
+    content: @Composable () -> Unit
+) {
+    val rememberedColorScheme = remember {
+        // Explicitly creating a new object here so we don't mutate the initial [colorScheme]
+        // provided, and overwrite the values set in it.
+        colorScheme.copy()
+    }.apply {
+        updateColorSchemeFrom(colorScheme)
+    }
+    val selectionColors = rememberTextSelectionColors(rememberedColorScheme)
+    CompositionLocalProvider(
+        LocalColorScheme provides rememberedColorScheme,
+        LocalShapes provides shapes,
+        LocalTextSelectionColors provides selectionColors,
+        LocalTypography provides typography
+    ) {
+        ProvideTextStyle(value = typography.bodyLarge, content = content)
+    }
+}
+
+/**
+ * Contains functions to access the current theme values provided at the call site's position in
+ * the hierarchy.
+ */
+object MaterialTheme {
+    /**
+     * Retrieves the current [ColorScheme] at the call site's position in the hierarchy.
+     */
+    @ExperimentalTvMaterial3Api
+    val colorScheme: ColorScheme
+        @Composable
+        @ReadOnlyComposable
+        @SuppressWarnings("HiddenTypeParameter", "UnavailableSymbol")
+        get() = LocalColorScheme.current
+
+    /**
+     * Retrieves the current [Typography] at the call site's position in the hierarchy.
+     */
+    val typography: Typography
+        @Composable
+        @ReadOnlyComposable
+        get() = LocalTypography.current
+
+    /**
+     * Retrieves the current [Shapes] at the call site's position in the hierarchy.
+     */
+    val shapes: Shapes
+        @Composable
+        @ReadOnlyComposable
+        get() = LocalShapes.current
+}
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+/*@VisibleForTesting*/
+internal fun rememberTextSelectionColors(colorScheme: ColorScheme): TextSelectionColors {
+    val primaryColor = colorScheme.primary
+    return remember(primaryColor) {
+        TextSelectionColors(
+            handleColor = primaryColor,
+            backgroundColor = primaryColor.copy(alpha = TextSelectionBackgroundOpacity)
+        )
+    }
+}
+
+/*@VisibleForTesting*/
+internal const val TextSelectionBackgroundOpacity = 0.4f
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Shapes.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Shapes.kt
new file mode 100644
index 0000000..9398128
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Shapes.kt
@@ -0,0 +1,180 @@
+/*
+ * 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.tv.material3
+
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.CornerBasedShape
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.dp
+import androidx.tv.material3.tokens.ShapeKeyTokens
+import androidx.tv.material3.tokens.ShapeTokens
+
+/**
+ * Material surfaces can be displayed in different shapes. Shapes direct attention, identify
+ * components, communicate state, and express brand.
+ *
+ * The shape scale defines the style of container corners, offering a range of roundedness from
+ * square to fully circular.
+ *
+ * There are different sizes of shapes:
+ * - Extra Small
+ * - Small
+ * - Medium
+ * - Large
+ * - Extra Large
+ *
+ * You can customize the shape system for all components in the [MaterialTheme] or you can do it
+ * on a per component basis.
+ *
+ * You can change the shape that a component has by overriding the shape parameter for that
+ * component. For example, by default, buttons use the shape style “full.” If your product requires
+ * a smaller amount of roundedness, you can override the shape parameter with a different shape
+ * value like MaterialTheme.shapes.small.
+ *
+ * To learn more about shapes, see [Material Design shapes](https://m3.material.io/styles/shape/overview).
+ *
+ * @param extraSmall A shape style with 4 same-sized corners whose size are bigger than
+ * [RectangleShape] and smaller than [Shapes.small]. By default autocomplete menu, select menu,
+ * snackbars, standard menu, and text fields use this shape.
+ * @param small A shape style with 4 same-sized corners whose size are bigger than
+ * [Shapes.extraSmall] and smaller than [Shapes.medium]. By default chips use this shape.
+ * @param medium A shape style with 4 same-sized corners whose size are bigger than [Shapes.small]
+ * and smaller than [Shapes.large]. By default cards and small FABs use this shape.
+ * @param large A shape style with 4 same-sized corners whose size are bigger than [Shapes.medium]
+ * and smaller than [Shapes.extraLarge]. By default extended FABs, FABs, and navigation drawers use
+ * this shape.
+ * @param extraLarge A shape style with 4 same-sized corners whose size are bigger than
+ * [Shapes.large] and smaller than [CircleShape]. By default large FABs use this shape.
+ */
+@Immutable
+class Shapes(
+    // Shapes None and Full are omitted as None is a RectangleShape and Full is a CircleShape.
+    val extraSmall: CornerBasedShape = ShapeDefaults.ExtraSmall,
+    val small: CornerBasedShape = ShapeDefaults.Small,
+    val medium: CornerBasedShape = ShapeDefaults.Medium,
+    val large: CornerBasedShape = ShapeDefaults.Large,
+    val extraLarge: CornerBasedShape = ShapeDefaults.ExtraLarge
+) {
+    /** Returns a copy of this Shapes, optionally overriding some of the values. */
+    fun copy(
+        extraSmall: CornerBasedShape = this.extraSmall,
+        small: CornerBasedShape = this.small,
+        medium: CornerBasedShape = this.medium,
+        large: CornerBasedShape = this.large,
+        extraLarge: CornerBasedShape = this.extraLarge
+    ): Shapes = Shapes(
+        extraSmall = extraSmall,
+        small = small,
+        medium = medium,
+        large = large,
+        extraLarge = extraLarge
+    )
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Shapes) return false
+        if (extraSmall != other.extraSmall) return false
+        if (small != other.small) return false
+        if (medium != other.medium) return false
+        if (large != other.large) return false
+        if (extraLarge != other.extraLarge) return false
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = extraSmall.hashCode()
+        result = 31 * result + small.hashCode()
+        result = 31 * result + medium.hashCode()
+        result = 31 * result + large.hashCode()
+        result = 31 * result + extraLarge.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "Shapes(" +
+            "extraSmall=$extraSmall, " +
+            "small=$small, " +
+            "medium=$medium, " +
+            "large=$large, " +
+            "extraLarge=$extraLarge)"
+    }
+}
+
+/**
+ * Contains the default values used by [Shapes]
+ */
+object ShapeDefaults {
+    /** Extra small sized corner shape */
+    val ExtraSmall: CornerBasedShape = ShapeTokens.CornerExtraSmall
+
+    /** Small sized corner shape */
+    val Small: CornerBasedShape = ShapeTokens.CornerSmall
+
+    /** Medium sized corner shape */
+    val Medium: CornerBasedShape = ShapeTokens.CornerMedium
+
+    /** Large sized corner shape */
+    val Large: CornerBasedShape = ShapeTokens.CornerLarge
+
+    /** Extra large sized corner shape */
+    val ExtraLarge: CornerBasedShape = ShapeTokens.CornerExtraLarge
+}
+
+/** Helper function for component shape tokens. Used to grab the top values of a shape parameter. */
+internal fun CornerBasedShape.top(): CornerBasedShape {
+    return copy(bottomStart = CornerSize(0.0.dp), bottomEnd = CornerSize(0.0.dp))
+}
+
+/** Helper function for component shape tokens. Used to grab the end values of a shape parameter. */
+internal fun CornerBasedShape.end(): CornerBasedShape {
+    return copy(topStart = CornerSize(0.0.dp), bottomStart = CornerSize(0.0.dp))
+}
+
+/**
+ * Helper function for component shape tokens. Here is an example on how to use component color
+ * tokens:
+ * ``MaterialTheme.shapes.fromToken(FabPrimarySmallTokens.ContainerShape)``
+ */
+internal fun Shapes.fromToken(value: ShapeKeyTokens): Shape {
+    return when (value) {
+        ShapeKeyTokens.CornerExtraLarge -> extraLarge
+        ShapeKeyTokens.CornerExtraLargeTop -> extraLarge.top()
+        ShapeKeyTokens.CornerExtraSmall -> extraSmall
+        ShapeKeyTokens.CornerExtraSmallTop -> extraSmall.top()
+        ShapeKeyTokens.CornerFull -> CircleShape
+        ShapeKeyTokens.CornerLarge -> large
+        ShapeKeyTokens.CornerLargeEnd -> large.end()
+        ShapeKeyTokens.CornerLargeTop -> large.top()
+        ShapeKeyTokens.CornerMedium -> medium
+        ShapeKeyTokens.CornerNone -> RectangleShape
+        ShapeKeyTokens.CornerSmall -> small
+    }
+}
+
+/** Converts a shape token key to the local shape provided by the theme */
+@Composable
+internal fun ShapeKeyTokens.toShape(): Shape {
+    return MaterialTheme.shapes.fromToken(this)
+}
+
+/** CompositionLocal used to specify the default shapes for the surfaces. */
+internal val LocalShapes = staticCompositionLocalOf { Shapes() }
diff --git a/tv/tv-material/src/main/java/androidx/tv/material/Tab.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Tab.kt
similarity index 99%
rename from tv/tv-material/src/main/java/androidx/tv/material/Tab.kt
rename to tv/tv-material/src/main/java/androidx/tv/material3/Tab.kt
index 48b306e..792f0c9 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material/Tab.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Tab.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.tv.material
+package androidx.tv.material3
 
 import androidx.compose.animation.animateColorAsState
 import androidx.compose.foundation.clickable
diff --git a/tv/tv-material/src/main/java/androidx/tv/material/TabRow.kt b/tv/tv-material/src/main/java/androidx/tv/material3/TabRow.kt
similarity index 99%
rename from tv/tv-material/src/main/java/androidx/tv/material/TabRow.kt
rename to tv/tv-material/src/main/java/androidx/tv/material3/TabRow.kt
index 64f0c77..d07a6a7 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material/TabRow.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/TabRow.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.tv.material
+package androidx.tv.material3
 
 import androidx.compose.animation.animateColorAsState
 import androidx.compose.animation.core.animateDpAsState
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Text.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Text.kt
new file mode 100644
index 0000000..cd7aaec
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Text.kt
@@ -0,0 +1,261 @@
+/*
+ * 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.tv.material3
+
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.foundation.text.InlineTextContent
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.compositionLocalOf
+import androidx.compose.runtime.structuralEqualityPolicy
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.takeOrElse
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.Paragraph
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.TextUnit
+
+/**
+ * High level element that displays text and provides semantics / accessibility information.
+ *
+ * The default [style] uses the [LocalTextStyle] provided by the [MaterialTheme] / components. If
+ * you are setting your own style, you may want to consider first retrieving [LocalTextStyle],
+ * and using [TextStyle.copy] to keep any theme defined attributes, only modifying the specific
+ * attributes you want to override.
+ *
+ * For ease of use, commonly used parameters from [TextStyle] are also present here. The order of
+ * precedence is as follows:
+ * - If a parameter is explicitly set here (i.e, it is _not_ `null` or [TextUnit.Unspecified]),
+ * then this parameter will always be used.
+ * - If a parameter is _not_ set, (`null` or [TextUnit.Unspecified]), then the corresponding value
+ * from [style] will be used instead.
+ *
+ * Additionally, for [color], if [color] is not set, and [style] does not have a color, then
+ * [LocalContentColor] will be used.
+ *
+ * @param text the text to be displayed
+ * @param modifier the [Modifier] to be applied to this layout node
+ * @param color [Color] to apply to the text. If [Color.Unspecified], and [style] has no color set,
+ * this will be [LocalContentColor].
+ * @param fontSize the size of glyphs to use when painting the text. See [TextStyle.fontSize].
+ * @param fontStyle the typeface variant to use when drawing the letters (e.g., italic).
+ * See [TextStyle.fontStyle].
+ * @param fontWeight the typeface thickness to use when painting the text (e.g., [FontWeight.Bold]).
+ * @param fontFamily the font family to be used when rendering the text. See [TextStyle.fontFamily].
+ * @param letterSpacing the amount of space to add between each letter.
+ * See [TextStyle.letterSpacing].
+ * @param textDecoration the decorations to paint on the text (e.g., an underline).
+ * See [TextStyle.textDecoration].
+ * @param textAlign the alignment of the text within the lines of the paragraph.
+ * See [TextStyle.textAlign].
+ * @param lineHeight line height for the [Paragraph] in [TextUnit] unit, e.g. SP or EM.
+ * See [TextStyle.lineHeight].
+ * @param overflow how visual overflow should be handled.
+ * @param softWrap whether the text should break at soft line breaks. If false, the glyphs in the
+ * text will be positioned as if there was unlimited horizontal space. If [softWrap] is false,
+ * [overflow] and TextAlign may have unexpected effects.
+ * @param maxLines an optional maximum number of lines for the text to span, wrapping if
+ * necessary. If the text exceeds the given number of lines, it will be truncated according to
+ * [overflow] and [softWrap]. If it is not null, then it must be greater than zero.
+ * @param onTextLayout callback that is executed when a new text layout is calculated. A
+ * [TextLayoutResult] object that callback provides contains paragraph information, size of the
+ * text, baselines and other details. The callback can be used to add additional decoration or
+ * functionality to the text. For example, to draw selection around the text.
+ * @param style style configuration for the text such as color, font, line height etc.
+ */
+@Composable
+fun Text(
+    text: String,
+    modifier: Modifier = Modifier,
+    color: Color = Color.Unspecified,
+    fontSize: TextUnit = TextUnit.Unspecified,
+    fontStyle: FontStyle? = null,
+    fontWeight: FontWeight? = null,
+    fontFamily: FontFamily? = null,
+    letterSpacing: TextUnit = TextUnit.Unspecified,
+    textDecoration: TextDecoration? = null,
+    textAlign: TextAlign? = null,
+    lineHeight: TextUnit = TextUnit.Unspecified,
+    overflow: TextOverflow = TextOverflow.Clip,
+    softWrap: Boolean = true,
+    maxLines: Int = Int.MAX_VALUE,
+    onTextLayout: (TextLayoutResult) -> Unit = {},
+    style: TextStyle = LocalTextStyle.current
+) {
+    val textColor = color.takeOrElse {
+        style.color.takeOrElse {
+            LocalContentColor.current
+        }
+    }
+    // NOTE(text-perf-review): It might be worthwhile writing a bespoke merge implementation that
+    // will avoid reallocating if all of the options here are the defaults
+    val mergedStyle = style.merge(
+        TextStyle(
+            color = textColor,
+            fontSize = fontSize,
+            fontWeight = fontWeight,
+            textAlign = textAlign,
+            lineHeight = lineHeight,
+            fontFamily = fontFamily,
+            textDecoration = textDecoration,
+            fontStyle = fontStyle,
+            letterSpacing = letterSpacing
+        )
+    )
+    BasicText(
+        text,
+        modifier,
+        mergedStyle,
+        onTextLayout,
+        overflow,
+        softWrap,
+        maxLines
+    )
+}
+
+/**
+ * High level element that displays text and provides semantics / accessibility information.
+ *
+ * The default [style] uses the [LocalTextStyle] provided by the [MaterialTheme] / components. If
+ * you are setting your own style, you may want to consider first retrieving [LocalTextStyle],
+ * and using [TextStyle.copy] to keep any theme defined attributes, only modifying the specific
+ * attributes you want to override.
+ *
+ * For ease of use, commonly used parameters from [TextStyle] are also present here. The order of
+ * precedence is as follows:
+ * - If a parameter is explicitly set here (i.e, it is _not_ `null` or [TextUnit.Unspecified]),
+ * then this parameter will always be used.
+ * - If a parameter is _not_ set, (`null` or [TextUnit.Unspecified]), then the corresponding value
+ * from [style] will be used instead.
+ *
+ * Additionally, for [color], if [color] is not set, and [style] does not have a color, then
+ * [LocalContentColor] will be used.
+ *
+ * @param text the text to be displayed
+ * @param modifier the [Modifier] to be applied to this layout node
+ * @param color [Color] to apply to the text. If [Color.Unspecified], and [style] has no color set,
+ * this will be [LocalContentColor].
+ * @param fontSize the size of glyphs to use when painting the text. See [TextStyle.fontSize].
+ * @param fontStyle the typeface variant to use when drawing the letters (e.g., italic).
+ * See [TextStyle.fontStyle].
+ * @param fontWeight the typeface thickness to use when painting the text (e.g., [FontWeight.Bold]).
+ * @param fontFamily the font family to be used when rendering the text. See [TextStyle.fontFamily].
+ * @param letterSpacing the amount of space to add between each letter.
+ * See [TextStyle.letterSpacing].
+ * @param textDecoration the decorations to paint on the text (e.g., an underline).
+ * See [TextStyle.textDecoration].
+ * @param textAlign the alignment of the text within the lines of the paragraph.
+ * See [TextStyle.textAlign].
+ * @param lineHeight line height for the [Paragraph] in [TextUnit] unit, e.g. SP or EM.
+ * See [TextStyle.lineHeight].
+ * @param overflow how visual overflow should be handled.
+ * @param softWrap whether the text should break at soft line breaks. If false, the glyphs in the
+ * text will be positioned as if there was unlimited horizontal space. If [softWrap] is false,
+ * [overflow] and TextAlign may have unexpected effects.
+ * @param maxLines an optional maximum number of lines for the text to span, wrapping if
+ * necessary. If the text exceeds the given number of lines, it will be truncated according to
+ * [overflow] and [softWrap]. If it is not null, then it must be greater than zero.
+ * @param inlineContent a map storing composables that replaces certain ranges of the text, used to
+ * insert composables into text layout. See [InlineTextContent].
+ * @param onTextLayout callback that is executed when a new text layout is calculated. A
+ * [TextLayoutResult] object that callback provides contains paragraph information, size of the
+ * text, baselines and other details. The callback can be used to add additional decoration or
+ * functionality to the text. For example, to draw selection around the text.
+ * @param style style configuration for the text such as color, font, line height etc.
+ */
+@Composable
+fun Text(
+    text: AnnotatedString,
+    modifier: Modifier = Modifier,
+    color: Color = Color.Unspecified,
+    fontSize: TextUnit = TextUnit.Unspecified,
+    fontStyle: FontStyle? = null,
+    fontWeight: FontWeight? = null,
+    fontFamily: FontFamily? = null,
+    letterSpacing: TextUnit = TextUnit.Unspecified,
+    textDecoration: TextDecoration? = null,
+    textAlign: TextAlign? = null,
+    lineHeight: TextUnit = TextUnit.Unspecified,
+    overflow: TextOverflow = TextOverflow.Clip,
+    softWrap: Boolean = true,
+    maxLines: Int = Int.MAX_VALUE,
+    inlineContent: Map<String, InlineTextContent> = mapOf(),
+    onTextLayout: (TextLayoutResult) -> Unit = {},
+    style: TextStyle = LocalTextStyle.current
+) {
+    val textColor = color.takeOrElse {
+        style.color.takeOrElse {
+            LocalContentColor.current
+        }
+    }
+    // NOTE(text-perf-review): It might be worthwhile writing a bespoke merge implementation that
+    // will avoid reallocating if all of the options here are the defaults
+    val mergedStyle = style.merge(
+        TextStyle(
+            color = textColor,
+            fontSize = fontSize,
+            fontWeight = fontWeight,
+            textAlign = textAlign,
+            lineHeight = lineHeight,
+            fontFamily = fontFamily,
+            textDecoration = textDecoration,
+            fontStyle = fontStyle,
+            letterSpacing = letterSpacing
+        )
+    )
+    BasicText(
+        text = text,
+        modifier = modifier,
+        style = mergedStyle,
+        onTextLayout = onTextLayout,
+        overflow = overflow,
+        softWrap = softWrap,
+        maxLines = maxLines,
+        inlineContent = inlineContent
+    )
+}
+
+/**
+ * CompositionLocal containing the preferred [TextStyle] that will be used by [Text] components by
+ * default. To set the value for this CompositionLocal, see [ProvideTextStyle] which will merge any
+ * missing [TextStyle] properties with the existing [TextStyle] set in this CompositionLocal.
+ *
+ * @see ProvideTextStyle
+ */
+val LocalTextStyle = compositionLocalOf(structuralEqualityPolicy()) { TextStyle.Default }
+
+// TODO(b/156598010): remove this and replace with fold definition on the backing CompositionLocal
+/**
+ * This function is used to set the current value of [LocalTextStyle], merging the given style
+ * with the current style values for any missing attributes. Any [Text] components included in
+ * this component's [content] will be styled with this style unless styled explicitly.
+ *
+ * @see LocalTextStyle
+ */
+@Composable
+fun ProvideTextStyle(value: TextStyle, content: @Composable () -> Unit) {
+    val mergedStyle = LocalTextStyle.current.merge(value)
+    CompositionLocalProvider(LocalTextStyle provides mergedStyle, content = content)
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Typography.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Typography.kt
new file mode 100644
index 0000000..014694b
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Typography.kt
@@ -0,0 +1,208 @@
+/*
+ * 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.tv.material3
+
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.text.TextStyle
+import androidx.tv.material3.tokens.TypographyKeyTokens
+import androidx.tv.material3.tokens.TypographyTokens
+
+/**
+ * The Material Design type scale includes a range of contrasting styles that support the needs of
+ * your product and its content.
+ *
+ * Use typography to make writing legible and beautiful. Material's default type scale includes
+ * contrasting and flexible styles to support a wide range of use cases.
+ *
+ * The type scale is a combination of thirteen styles that are supported by the type system. It
+ * contains reusable categories of text, each with an intended application and meaning.
+ *
+ * To learn more about typography, see [Material Design typography](https://m3.material.io/styles/typography/overview).
+ *
+ * @property displayLarge displayLarge is the largest display text.
+ * @property displayMedium displayMedium is the second largest display text.
+ * @property displaySmall displaySmall is the smallest display text.
+ * @property headlineLarge headlineLarge is the largest headline, reserved for short, important text
+ * or numerals. For headlines, you can choose an expressive font, such as a display, handwritten, or
+ * script style. These unconventional font designs have details and intricacy that help attract the
+ * eye.
+ * @property headlineMedium headlineMedium is the second largest headline, reserved for short,
+ * important text or numerals. For headlines, you can choose an expressive font, such as a display,
+ * handwritten, or script style. These unconventional font designs have details and intricacy that
+ * help attract the eye.
+ * @property headlineSmall headlineSmall is the smallest headline, reserved for short, important
+ * text or numerals. For headlines, you can choose an expressive font, such as a display,
+ * handwritten, or script style. These unconventional font designs have details and intricacy that
+ * help attract the eye.
+ * @property titleLarge titleLarge is the largest title, and is typically reserved for
+ * medium-emphasis text that is shorter in length. Serif or sans serif typefaces work well for
+ * subtitles.
+ * @property titleMedium titleMedium is the second largest title, and is typically reserved for
+ * medium-emphasis text that is shorter in length. Serif or sans serif typefaces work well for
+ * subtitles.
+ * @property titleSmall titleSmall is the smallest title, and is typically reserved for
+ * medium-emphasis text that is shorter in length. Serif or sans serif typefaces work well for
+ * subtitles.
+ * @property bodyLarge bodyLarge is the largest body, and is typically used for long-form writing as
+ * it works well for small text sizes. For longer sections of text, a serif or sans serif typeface
+ * is recommended.
+ * @property bodyMedium bodyMedium is the second largest body, and is typically used for long-form
+ * writing as it works well for small text sizes. For longer sections of text, a serif or sans serif
+ * typeface is recommended.
+ * @property bodySmall bodySmall is the smallest body, and is typically used for long-form writing
+ * as it works well for small text sizes. For longer sections of text, a serif or sans serif
+ * typeface is recommended.
+ * @property labelLarge labelLarge text is a call to action used in different types of buttons (such
+ * as text, outlined and contained buttons) and in tabs, dialogs, and cards. Button text is
+ * typically sans serif, using all caps text.
+ * @property labelMedium labelMedium is one of the smallest font sizes. It is used sparingly to
+ * annotate imagery or to introduce a headline.
+ * @property labelSmall labelSmall is one of the smallest font sizes. It is used sparingly to
+ * annotate imagery or to introduce a headline.
+ */
+@Immutable
+class Typography(
+    val displayLarge: TextStyle = TypographyTokens.DisplayLarge,
+    val displayMedium: TextStyle = TypographyTokens.DisplayMedium,
+    val displaySmall: TextStyle = TypographyTokens.DisplaySmall,
+    val headlineLarge: TextStyle = TypographyTokens.HeadlineLarge,
+    val headlineMedium: TextStyle = TypographyTokens.HeadlineMedium,
+    val headlineSmall: TextStyle = TypographyTokens.HeadlineSmall,
+    val titleLarge: TextStyle = TypographyTokens.TitleLarge,
+    val titleMedium: TextStyle = TypographyTokens.TitleMedium,
+    val titleSmall: TextStyle = TypographyTokens.TitleSmall,
+    val bodyLarge: TextStyle = TypographyTokens.BodyLarge,
+    val bodyMedium: TextStyle = TypographyTokens.BodyMedium,
+    val bodySmall: TextStyle = TypographyTokens.BodySmall,
+    val labelLarge: TextStyle = TypographyTokens.LabelLarge,
+    val labelMedium: TextStyle = TypographyTokens.LabelMedium,
+    val labelSmall: TextStyle = TypographyTokens.LabelSmall
+) {
+
+    /** Returns a copy of this Typography, optionally overriding some of the values. */
+    fun copy(
+        displayLarge: TextStyle = this.displayLarge,
+        displayMedium: TextStyle = this.displayMedium,
+        displaySmall: TextStyle = this.displaySmall,
+        headlineLarge: TextStyle = this.headlineLarge,
+        headlineMedium: TextStyle = this.headlineMedium,
+        headlineSmall: TextStyle = this.headlineSmall,
+        titleLarge: TextStyle = this.titleLarge,
+        titleMedium: TextStyle = this.titleMedium,
+        titleSmall: TextStyle = this.titleSmall,
+        bodyLarge: TextStyle = this.bodyLarge,
+        bodyMedium: TextStyle = this.bodyMedium,
+        bodySmall: TextStyle = this.bodySmall,
+        labelLarge: TextStyle = this.labelLarge,
+        labelMedium: TextStyle = this.labelMedium,
+        labelSmall: TextStyle = this.labelSmall
+    ): Typography =
+        Typography(
+            displayLarge = displayLarge,
+            displayMedium = displayMedium,
+            displaySmall = displaySmall,
+            headlineLarge = headlineLarge,
+            headlineMedium = headlineMedium,
+            headlineSmall = headlineSmall,
+            titleLarge = titleLarge,
+            titleMedium = titleMedium,
+            titleSmall = titleSmall,
+            bodyLarge = bodyLarge,
+            bodyMedium = bodyMedium,
+            bodySmall = bodySmall,
+            labelLarge = labelLarge,
+            labelMedium = labelMedium,
+            labelSmall = labelSmall
+        )
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is Typography) return false
+
+        if (displayLarge != other.displayLarge) return false
+        if (displayMedium != other.displayMedium) return false
+        if (displaySmall != other.displaySmall) return false
+        if (headlineLarge != other.headlineLarge) return false
+        if (headlineMedium != other.headlineMedium) return false
+        if (headlineSmall != other.headlineSmall) return false
+        if (titleLarge != other.titleLarge) return false
+        if (titleMedium != other.titleMedium) return false
+        if (titleSmall != other.titleSmall) return false
+        if (bodyLarge != other.bodyLarge) return false
+        if (bodyMedium != other.bodyMedium) return false
+        if (bodySmall != other.bodySmall) return false
+        if (labelLarge != other.labelLarge) return false
+        if (labelMedium != other.labelMedium) return false
+        if (labelSmall != other.labelSmall) return false
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = displayLarge.hashCode()
+        result = 31 * result + displayMedium.hashCode()
+        result = 31 * result + displaySmall.hashCode()
+        result = 31 * result + headlineLarge.hashCode()
+        result = 31 * result + headlineMedium.hashCode()
+        result = 31 * result + headlineSmall.hashCode()
+        result = 31 * result + titleLarge.hashCode()
+        result = 31 * result + titleMedium.hashCode()
+        result = 31 * result + titleSmall.hashCode()
+        result = 31 * result + bodyLarge.hashCode()
+        result = 31 * result + bodyMedium.hashCode()
+        result = 31 * result + bodySmall.hashCode()
+        result = 31 * result + labelLarge.hashCode()
+        result = 31 * result + labelMedium.hashCode()
+        result = 31 * result + labelSmall.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "Typography(displayLarge=$displayLarge, displayMedium=$displayMedium," +
+            "displaySmall=$displaySmall, " +
+            "headlineLarge=$headlineLarge, headlineMedium=$headlineMedium," +
+            " headlineSmall=$headlineSmall, " +
+            "titleLarge=$titleLarge, titleMedium=$titleMedium, titleSmall=$titleSmall, " +
+            "bodyLarge=$bodyLarge, bodyMedium=$bodyMedium, bodySmall=$bodySmall, " +
+            "labelLarge=$labelLarge, labelMedium=$labelMedium, labelSmall=$labelSmall)"
+    }
+}
+
+/**
+ * Helper function for component typography tokens.
+ */
+internal fun Typography.fromToken(value: TypographyKeyTokens): TextStyle {
+    return when (value) {
+        TypographyKeyTokens.DisplayLarge -> displayLarge
+        TypographyKeyTokens.DisplayMedium -> displayMedium
+        TypographyKeyTokens.DisplaySmall -> displaySmall
+        TypographyKeyTokens.HeadlineLarge -> headlineLarge
+        TypographyKeyTokens.HeadlineMedium -> headlineMedium
+        TypographyKeyTokens.HeadlineSmall -> headlineSmall
+        TypographyKeyTokens.TitleLarge -> titleLarge
+        TypographyKeyTokens.TitleMedium -> titleMedium
+        TypographyKeyTokens.TitleSmall -> titleSmall
+        TypographyKeyTokens.BodyLarge -> bodyLarge
+        TypographyKeyTokens.BodyMedium -> bodyMedium
+        TypographyKeyTokens.BodySmall -> bodySmall
+        TypographyKeyTokens.LabelLarge -> labelLarge
+        TypographyKeyTokens.LabelMedium -> labelMedium
+        TypographyKeyTokens.LabelSmall -> labelSmall
+    }
+}
+
+internal val LocalTypography = staticCompositionLocalOf { Typography() }
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/tokens/ColorDarkTokens.kt b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/ColorDarkTokens.kt
new file mode 100644
index 0000000..460f41e
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/ColorDarkTokens.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+// VERSION: v0_001 (Inspired by androidx.compose.material3.tokens v0_126)
+
+package androidx.tv.material3.tokens
+
+internal object ColorDarkTokens {
+    val Background = PaletteTokens.Neutral10
+    val Error = PaletteTokens.Error80
+    val ErrorContainer = PaletteTokens.Error30
+    val InverseOnSurface = PaletteTokens.Neutral20
+    val InversePrimary = PaletteTokens.Primary40
+    val InverseSurface = PaletteTokens.Neutral90
+    val OnBackground = PaletteTokens.Neutral90
+    val OnError = PaletteTokens.Error20
+    val OnErrorContainer = PaletteTokens.Error90
+    val OnPrimary = PaletteTokens.Primary20
+    val OnPrimaryContainer = PaletteTokens.Primary90
+    val OnSecondary = PaletteTokens.Secondary20
+    val OnSecondaryContainer = PaletteTokens.Secondary90
+    val OnSurface = PaletteTokens.Neutral90
+    val OnSurfaceVariant = PaletteTokens.NeutralVariant80
+    val OnTertiary = PaletteTokens.Tertiary20
+    val OnTertiaryContainer = PaletteTokens.Tertiary90
+    val Outline = PaletteTokens.NeutralVariant60
+    val OutlineVariant = PaletteTokens.NeutralVariant30
+    val Primary = PaletteTokens.Primary80
+    val PrimaryContainer = PaletteTokens.Primary30
+    val Scrim = PaletteTokens.Neutral0
+    val Secondary = PaletteTokens.Secondary80
+    val SecondaryContainer = PaletteTokens.Secondary30
+    val Surface = PaletteTokens.Neutral10
+    val SurfaceTint = Primary
+    val SurfaceVariant = PaletteTokens.NeutralVariant30
+    val Tertiary = PaletteTokens.Tertiary80
+    val TertiaryContainer = PaletteTokens.Tertiary30
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/tokens/ColorLightTokens.kt b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/ColorLightTokens.kt
new file mode 100644
index 0000000..261c8d8
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/ColorLightTokens.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+// VERSION: v0_001 (Inspired by androidx.compose.material3.tokens v0_103)
+
+package androidx.tv.material3.tokens
+
+internal object ColorLightTokens {
+    val Background = PaletteTokens.Neutral99
+    val Error = PaletteTokens.Error40
+    val ErrorContainer = PaletteTokens.Error90
+    val InverseOnSurface = PaletteTokens.Neutral95
+    val InversePrimary = PaletteTokens.Primary80
+    val InverseSurface = PaletteTokens.Neutral20
+    val OnBackground = PaletteTokens.Neutral10
+    val OnError = PaletteTokens.Error100
+    val OnErrorContainer = PaletteTokens.Error10
+    val OnPrimary = PaletteTokens.Primary100
+    val OnPrimaryContainer = PaletteTokens.Primary10
+    val OnSecondary = PaletteTokens.Secondary100
+    val OnSecondaryContainer = PaletteTokens.Secondary10
+    val OnSurface = PaletteTokens.Neutral10
+    val OnSurfaceVariant = PaletteTokens.NeutralVariant30
+    val OnTertiary = PaletteTokens.Tertiary100
+    val OnTertiaryContainer = PaletteTokens.Tertiary10
+    val Outline = PaletteTokens.NeutralVariant50
+    val OutlineVariant = PaletteTokens.NeutralVariant80
+    val Primary = PaletteTokens.Primary40
+    val PrimaryContainer = PaletteTokens.Primary90
+    val Scrim = PaletteTokens.Neutral0
+    val Secondary = PaletteTokens.Secondary40
+    val SecondaryContainer = PaletteTokens.Secondary90
+    val Surface = PaletteTokens.Neutral99
+    val SurfaceTint = Primary
+    val SurfaceVariant = PaletteTokens.NeutralVariant90
+    val Tertiary = PaletteTokens.Tertiary40
+    val TertiaryContainer = PaletteTokens.Tertiary90
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/tokens/ColorSchemeKeyTokens.kt b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/ColorSchemeKeyTokens.kt
new file mode 100644
index 0000000..ccc622a
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/ColorSchemeKeyTokens.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+// VERSION: v0_001 (Inspired by androidx.compose.material3.tokens v0_103)
+
+package androidx.tv.material3.tokens
+
+internal enum class ColorSchemeKeyTokens {
+    Background,
+    Error,
+    ErrorContainer,
+    InverseOnSurface,
+    InversePrimary,
+    InverseSurface,
+    OnBackground,
+    OnError,
+    OnErrorContainer,
+    OnPrimary,
+    OnPrimaryContainer,
+    OnSecondary,
+    OnSecondaryContainer,
+    OnSurface,
+    OnSurfaceVariant,
+    OnTertiary,
+    OnTertiaryContainer,
+    Outline,
+    OutlineVariant,
+    Primary,
+    PrimaryContainer,
+    Scrim,
+    Secondary,
+    SecondaryContainer,
+    Surface,
+    SurfaceTint,
+    SurfaceVariant,
+    Tertiary,
+    TertiaryContainer,
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/Elevation.kt
similarity index 60%
copy from tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
copy to tv/tv-material/src/main/java/androidx/tv/material3/tokens/Elevation.kt
index ebac64e..fef575f 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/Elevation.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * 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.
@@ -13,11 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+// VERSION: v0_001 (Inspired by androidx.compose.material3.tokens v0_103)
 
-package androidx.tv.material
+package androidx.tv.material3.tokens
 
-@RequiresOptIn(
-    "This tv-material API is experimental and likely to change or be removed in the future."
-)
-@Retention(AnnotationRetention.BINARY)
-annotation class ExperimentalTvMaterialApi
\ No newline at end of file
+import androidx.compose.ui.unit.dp
+
+internal object Elevation {
+    val Level0 = 0.0.dp
+    val Level1 = 1.0.dp
+    val Level2 = 3.0.dp
+    val Level3 = 6.0.dp
+    val Level4 = 8.0.dp
+    val Level5 = 12.0.dp
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/tokens/PaletteTokens.kt b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/PaletteTokens.kt
new file mode 100644
index 0000000..419b2bb
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/PaletteTokens.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+// VERSION: v0_001 (Inspired by androidx.compose.material3.tokens v0_103)
+
+package androidx.tv.material3.tokens
+
+import androidx.compose.ui.graphics.Color
+
+internal object PaletteTokens {
+    val Black = Color(red = 0, green = 0, blue = 0)
+    val Error0 = Color(red = 0, green = 0, blue = 0)
+    val Error10 = Color(red = 65, green = 14, blue = 11)
+    val Error100 = Color(red = 255, green = 255, blue = 255)
+    val Error20 = Color(red = 96, green = 20, blue = 16)
+    val Error30 = Color(red = 140, green = 29, blue = 24)
+    val Error40 = Color(red = 179, green = 38, blue = 30)
+    val Error50 = Color(red = 220, green = 54, blue = 46)
+    val Error60 = Color(red = 228, green = 105, blue = 98)
+    val Error70 = Color(red = 236, green = 146, blue = 142)
+    val Error80 = Color(red = 242, green = 184, blue = 181)
+    val Error90 = Color(red = 249, green = 222, blue = 220)
+    val Error95 = Color(red = 252, green = 238, blue = 238)
+    val Error99 = Color(red = 255, green = 251, blue = 249)
+    val Neutral0 = Color(red = 0, green = 0, blue = 0)
+    val Neutral10 = Color(red = 28, green = 27, blue = 31)
+    val Neutral100 = Color(red = 255, green = 255, blue = 255)
+    val Neutral20 = Color(red = 49, green = 48, blue = 51)
+    val Neutral30 = Color(red = 72, green = 70, blue = 73)
+    val Neutral40 = Color(red = 96, green = 93, blue = 98)
+    val Neutral50 = Color(red = 120, green = 117, blue = 121)
+    val Neutral60 = Color(red = 147, green = 144, blue = 148)
+    val Neutral70 = Color(red = 174, green = 170, blue = 174)
+    val Neutral80 = Color(red = 201, green = 197, blue = 202)
+    val Neutral90 = Color(red = 230, green = 225, blue = 229)
+    val Neutral95 = Color(red = 244, green = 239, blue = 244)
+    val Neutral99 = Color(red = 255, green = 251, blue = 254)
+    val NeutralVariant0 = Color(red = 0, green = 0, blue = 0)
+    val NeutralVariant10 = Color(red = 29, green = 26, blue = 34)
+    val NeutralVariant100 = Color(red = 255, green = 255, blue = 255)
+    val NeutralVariant20 = Color(red = 50, green = 47, blue = 55)
+    val NeutralVariant30 = Color(red = 73, green = 69, blue = 79)
+    val NeutralVariant40 = Color(red = 96, green = 93, blue = 102)
+    val NeutralVariant50 = Color(red = 121, green = 116, blue = 126)
+    val NeutralVariant60 = Color(red = 147, green = 143, blue = 153)
+    val NeutralVariant70 = Color(red = 174, green = 169, blue = 180)
+    val NeutralVariant80 = Color(red = 202, green = 196, blue = 208)
+    val NeutralVariant90 = Color(red = 231, green = 224, blue = 236)
+    val NeutralVariant95 = Color(red = 245, green = 238, blue = 250)
+    val NeutralVariant99 = Color(red = 255, green = 251, blue = 254)
+    val Primary0 = Color(red = 0, green = 0, blue = 0)
+    val Primary10 = Color(red = 33, green = 0, blue = 93)
+    val Primary100 = Color(red = 255, green = 255, blue = 255)
+    val Primary20 = Color(red = 56, green = 30, blue = 114)
+    val Primary30 = Color(red = 79, green = 55, blue = 139)
+    val Primary40 = Color(red = 103, green = 80, blue = 164)
+    val Primary50 = Color(red = 127, green = 103, blue = 190)
+    val Primary60 = Color(red = 154, green = 130, blue = 219)
+    val Primary70 = Color(red = 182, green = 157, blue = 248)
+    val Primary80 = Color(red = 208, green = 188, blue = 255)
+    val Primary90 = Color(red = 234, green = 221, blue = 255)
+    val Primary95 = Color(red = 246, green = 237, blue = 255)
+    val Primary99 = Color(red = 255, green = 251, blue = 254)
+    val Secondary0 = Color(red = 0, green = 0, blue = 0)
+    val Secondary10 = Color(red = 29, green = 25, blue = 43)
+    val Secondary100 = Color(red = 255, green = 255, blue = 255)
+    val Secondary20 = Color(red = 51, green = 45, blue = 65)
+    val Secondary30 = Color(red = 74, green = 68, blue = 88)
+    val Secondary40 = Color(red = 98, green = 91, blue = 113)
+    val Secondary50 = Color(red = 122, green = 114, blue = 137)
+    val Secondary60 = Color(red = 149, green = 141, blue = 165)
+    val Secondary70 = Color(red = 176, green = 167, blue = 192)
+    val Secondary80 = Color(red = 204, green = 194, blue = 220)
+    val Secondary90 = Color(red = 232, green = 222, blue = 248)
+    val Secondary95 = Color(red = 246, green = 237, blue = 255)
+    val Secondary99 = Color(red = 255, green = 251, blue = 254)
+    val Tertiary0 = Color(red = 0, green = 0, blue = 0)
+    val Tertiary10 = Color(red = 49, green = 17, blue = 29)
+    val Tertiary100 = Color(red = 255, green = 255, blue = 255)
+    val Tertiary20 = Color(red = 73, green = 37, blue = 50)
+    val Tertiary30 = Color(red = 99, green = 59, blue = 72)
+    val Tertiary40 = Color(red = 125, green = 82, blue = 96)
+    val Tertiary50 = Color(red = 152, green = 105, blue = 119)
+    val Tertiary60 = Color(red = 181, green = 131, blue = 146)
+    val Tertiary70 = Color(red = 210, green = 157, blue = 172)
+    val Tertiary80 = Color(red = 239, green = 184, blue = 200)
+    val Tertiary90 = Color(red = 255, green = 216, blue = 228)
+    val Tertiary95 = Color(red = 255, green = 236, blue = 241)
+    val Tertiary99 = Color(red = 255, green = 251, blue = 250)
+    val White = Color(red = 255, green = 255, blue = 255)
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/tokens/ShapeKeyTokens.kt b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/ShapeKeyTokens.kt
new file mode 100644
index 0000000..43ef4de
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/ShapeKeyTokens.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+// VERSION: v0_001 (Inspired by androidx.compose.material3.tokens v0_103)
+
+package androidx.tv.material3.tokens
+
+internal enum class ShapeKeyTokens {
+    CornerExtraLarge,
+    CornerExtraLargeTop,
+    CornerExtraSmall,
+    CornerExtraSmallTop,
+    CornerFull,
+    CornerLarge,
+    CornerLargeEnd,
+    CornerLargeTop,
+    CornerMedium,
+    CornerNone,
+    CornerSmall,
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/tokens/ShapeTokens.kt b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/ShapeTokens.kt
new file mode 100644
index 0000000..c027d0b
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/ShapeTokens.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+// VERSION: v0_001 (Inspired by androidx.compose.material3.tokens v0_103)
+
+package androidx.tv.material3.tokens
+
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.unit.dp
+
+internal object ShapeTokens {
+    val CornerExtraLarge = RoundedCornerShape(28.0.dp)
+    val CornerExtraLargeTop =
+        RoundedCornerShape(
+            topStart = 28.0.dp,
+            topEnd = 28.0.dp,
+            bottomEnd = 0.0.dp,
+            bottomStart = 0.0.dp
+        )
+    val CornerExtraSmall = RoundedCornerShape(4.0.dp)
+    val CornerExtraSmallTop = RoundedCornerShape(
+        topStart = 4.0.dp,
+        topEnd = 4.0.dp,
+        bottomEnd = 0.0.dp,
+        bottomStart = 0.0.dp
+    )
+    val CornerFull = CircleShape
+    val CornerLarge = RoundedCornerShape(16.0.dp)
+    val CornerLargeEnd =
+        RoundedCornerShape(
+            topStart = 0.0.dp,
+            topEnd = 16.0.dp,
+            bottomEnd = 16.0.dp,
+            bottomStart = 0.0.dp
+        )
+    val CornerLargeTop =
+        RoundedCornerShape(
+            topStart = 16.0.dp,
+            topEnd = 16.0.dp,
+            bottomEnd = 0.0.dp,
+            bottomStart = 0.0.dp
+        )
+    val CornerMedium = RoundedCornerShape(12.0.dp)
+    val CornerNone = RectangleShape
+    val CornerSmall = RoundedCornerShape(8.0.dp)
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/tokens/TypeScaleTokens.kt b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/TypeScaleTokens.kt
new file mode 100644
index 0000000..7091c76
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/TypeScaleTokens.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+// VERSION: v0_001 (Inspired by androidx.compose.material3.tokens v0_103)
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.tv.material3.tokens
+
+import androidx.compose.ui.unit.sp
+
+internal object TypeScaleTokens {
+    val BodyLargeFont = TypefaceTokens.Plain
+    val BodyLargeLineHeight = 24.0.sp
+    val BodyLargeSize = 16.sp
+    val BodyLargeTracking = 0.5.sp
+    val BodyLargeWeight = TypefaceTokens.WeightRegular
+    val BodyMediumFont = TypefaceTokens.Plain
+    val BodyMediumLineHeight = 20.0.sp
+    val BodyMediumSize = 14.sp
+    val BodyMediumTracking = 0.2.sp
+    val BodyMediumWeight = TypefaceTokens.WeightRegular
+    val BodySmallFont = TypefaceTokens.Plain
+    val BodySmallLineHeight = 16.0.sp
+    val BodySmallSize = 12.sp
+    val BodySmallTracking = 0.4.sp
+    val BodySmallWeight = TypefaceTokens.WeightRegular
+    val DisplayLargeFont = TypefaceTokens.Brand
+    val DisplayLargeLineHeight = 64.0.sp
+    val DisplayLargeSize = 57.sp
+    val DisplayLargeTracking = -0.2.sp
+    val DisplayLargeWeight = TypefaceTokens.WeightRegular
+    val DisplayMediumFont = TypefaceTokens.Brand
+    val DisplayMediumLineHeight = 52.0.sp
+    val DisplayMediumSize = 45.sp
+    val DisplayMediumTracking = 0.0.sp
+    val DisplayMediumWeight = TypefaceTokens.WeightRegular
+    val DisplaySmallFont = TypefaceTokens.Brand
+    val DisplaySmallLineHeight = 44.0.sp
+    val DisplaySmallSize = 36.sp
+    val DisplaySmallTracking = 0.0.sp
+    val DisplaySmallWeight = TypefaceTokens.WeightRegular
+    val HeadlineLargeFont = TypefaceTokens.Brand
+    val HeadlineLargeLineHeight = 40.0.sp
+    val HeadlineLargeSize = 32.sp
+    val HeadlineLargeTracking = 0.0.sp
+    val HeadlineLargeWeight = TypefaceTokens.WeightRegular
+    val HeadlineMediumFont = TypefaceTokens.Brand
+    val HeadlineMediumLineHeight = 36.0.sp
+    val HeadlineMediumSize = 28.sp
+    val HeadlineMediumTracking = 0.0.sp
+    val HeadlineMediumWeight = TypefaceTokens.WeightRegular
+    val HeadlineSmallFont = TypefaceTokens.Brand
+    val HeadlineSmallLineHeight = 32.0.sp
+    val HeadlineSmallSize = 24.sp
+    val HeadlineSmallTracking = 0.0.sp
+    val HeadlineSmallWeight = TypefaceTokens.WeightRegular
+    val LabelLargeFont = TypefaceTokens.Plain
+    val LabelLargeLineHeight = 20.0.sp
+    val LabelLargeSize = 14.sp
+    val LabelLargeTracking = 0.1.sp
+    val LabelLargeWeight = TypefaceTokens.WeightMedium
+    val LabelMediumFont = TypefaceTokens.Plain
+    val LabelMediumLineHeight = 16.0.sp
+    val LabelMediumSize = 12.sp
+    val LabelMediumTracking = 0.5.sp
+    val LabelMediumWeight = TypefaceTokens.WeightMedium
+    val LabelSmallFont = TypefaceTokens.Plain
+    val LabelSmallLineHeight = 16.0.sp
+    val LabelSmallSize = 11.sp
+    val LabelSmallTracking = 0.5.sp
+    val LabelSmallWeight = TypefaceTokens.WeightMedium
+    val TitleLargeFont = TypefaceTokens.Brand
+    val TitleLargeLineHeight = 28.0.sp
+    val TitleLargeSize = 22.sp
+    val TitleLargeTracking = 0.0.sp
+    val TitleLargeWeight = TypefaceTokens.WeightRegular
+    val TitleMediumFont = TypefaceTokens.Plain
+    val TitleMediumLineHeight = 24.0.sp
+    val TitleMediumSize = 16.sp
+    val TitleMediumTracking = 0.2.sp
+    val TitleMediumWeight = TypefaceTokens.WeightMedium
+    val TitleSmallFont = TypefaceTokens.Plain
+    val TitleSmallLineHeight = 20.0.sp
+    val TitleSmallSize = 14.sp
+    val TitleSmallTracking = 0.1.sp
+    val TitleSmallWeight = TypefaceTokens.WeightMedium
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/tokens/TypefaceTokens.kt b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/TypefaceTokens.kt
new file mode 100644
index 0000000..7d0084f
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/TypefaceTokens.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+// VERSION: v0_001 (Inspired by androidx.compose.material3.tokens v0_103)
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.tv.material3.tokens
+
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+
+internal object TypefaceTokens {
+    val Brand = FontFamily.SansSerif
+    val Plain = FontFamily.SansSerif
+    val WeightBold = FontWeight.Bold
+    val WeightMedium = FontWeight.Medium
+    val WeightRegular = FontWeight.Normal
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/tokens/TypographyKeyTokens.kt b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/TypographyKeyTokens.kt
new file mode 100644
index 0000000..a913a5a
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/TypographyKeyTokens.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+// VERSION: v0_001 (Inspired by androidx.compose.material3.tokens v0_103)
+
+package androidx.tv.material3.tokens
+
+internal enum class TypographyKeyTokens {
+    BodyLarge,
+    BodyMedium,
+    BodySmall,
+    DisplayLarge,
+    DisplayMedium,
+    DisplaySmall,
+    HeadlineLarge,
+    HeadlineMedium,
+    HeadlineSmall,
+    LabelLarge,
+    LabelMedium,
+    LabelSmall,
+    TitleLarge,
+    TitleMedium,
+    TitleSmall,
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/tokens/TypographyTokens.kt b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/TypographyTokens.kt
new file mode 100644
index 0000000..c7b5519
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/TypographyTokens.kt
@@ -0,0 +1,143 @@
+/*
+ * 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.
+ */
+// VERSION: v0_001 (Inspired by androidx.compose.material3.tokens v0_103)
+
+package androidx.tv.material3.tokens
+
+import androidx.compose.ui.text.TextStyle
+
+internal object TypographyTokens {
+    val BodyLarge =
+        TextStyle(
+            fontFamily = TypeScaleTokens.BodyLargeFont,
+            fontWeight = TypeScaleTokens.BodyLargeWeight,
+            fontSize = TypeScaleTokens.BodyLargeSize,
+            lineHeight = TypeScaleTokens.BodyLargeLineHeight,
+            letterSpacing = TypeScaleTokens.BodyLargeTracking
+        )
+    val BodyMedium =
+        TextStyle(
+            fontFamily = TypeScaleTokens.BodyMediumFont,
+            fontWeight = TypeScaleTokens.BodyMediumWeight,
+            fontSize = TypeScaleTokens.BodyMediumSize,
+            lineHeight = TypeScaleTokens.BodyMediumLineHeight,
+            letterSpacing = TypeScaleTokens.BodyMediumTracking
+        )
+    val BodySmall =
+        TextStyle(
+            fontFamily = TypeScaleTokens.BodySmallFont,
+            fontWeight = TypeScaleTokens.BodySmallWeight,
+            fontSize = TypeScaleTokens.BodySmallSize,
+            lineHeight = TypeScaleTokens.BodySmallLineHeight,
+            letterSpacing = TypeScaleTokens.BodySmallTracking
+        )
+    val DisplayLarge =
+        TextStyle(
+            fontFamily = TypeScaleTokens.DisplayLargeFont,
+            fontWeight = TypeScaleTokens.DisplayLargeWeight,
+            fontSize = TypeScaleTokens.DisplayLargeSize,
+            lineHeight = TypeScaleTokens.DisplayLargeLineHeight,
+            letterSpacing = TypeScaleTokens.DisplayLargeTracking
+        )
+    val DisplayMedium =
+        TextStyle(
+            fontFamily = TypeScaleTokens.DisplayMediumFont,
+            fontWeight = TypeScaleTokens.DisplayMediumWeight,
+            fontSize = TypeScaleTokens.DisplayMediumSize,
+            lineHeight = TypeScaleTokens.DisplayMediumLineHeight,
+            letterSpacing = TypeScaleTokens.DisplayMediumTracking
+        )
+    val DisplaySmall =
+        TextStyle(
+            fontFamily = TypeScaleTokens.DisplaySmallFont,
+            fontWeight = TypeScaleTokens.DisplaySmallWeight,
+            fontSize = TypeScaleTokens.DisplaySmallSize,
+            lineHeight = TypeScaleTokens.DisplaySmallLineHeight,
+            letterSpacing = TypeScaleTokens.DisplaySmallTracking
+        )
+    val HeadlineLarge =
+        TextStyle(
+            fontFamily = TypeScaleTokens.HeadlineLargeFont,
+            fontWeight = TypeScaleTokens.HeadlineLargeWeight,
+            fontSize = TypeScaleTokens.HeadlineLargeSize,
+            lineHeight = TypeScaleTokens.HeadlineLargeLineHeight,
+            letterSpacing = TypeScaleTokens.HeadlineLargeTracking
+        )
+    val HeadlineMedium =
+        TextStyle(
+            fontFamily = TypeScaleTokens.HeadlineMediumFont,
+            fontWeight = TypeScaleTokens.HeadlineMediumWeight,
+            fontSize = TypeScaleTokens.HeadlineMediumSize,
+            lineHeight = TypeScaleTokens.HeadlineMediumLineHeight,
+            letterSpacing = TypeScaleTokens.HeadlineMediumTracking
+        )
+    val HeadlineSmall =
+        TextStyle(
+            fontFamily = TypeScaleTokens.HeadlineSmallFont,
+            fontWeight = TypeScaleTokens.HeadlineSmallWeight,
+            fontSize = TypeScaleTokens.HeadlineSmallSize,
+            lineHeight = TypeScaleTokens.HeadlineSmallLineHeight,
+            letterSpacing = TypeScaleTokens.HeadlineSmallTracking
+        )
+    val LabelLarge =
+        TextStyle(
+            fontFamily = TypeScaleTokens.LabelLargeFont,
+            fontWeight = TypeScaleTokens.LabelLargeWeight,
+            fontSize = TypeScaleTokens.LabelLargeSize,
+            lineHeight = TypeScaleTokens.LabelLargeLineHeight,
+            letterSpacing = TypeScaleTokens.LabelLargeTracking
+        )
+    val LabelMedium =
+        TextStyle(
+            fontFamily = TypeScaleTokens.LabelMediumFont,
+            fontWeight = TypeScaleTokens.LabelMediumWeight,
+            fontSize = TypeScaleTokens.LabelMediumSize,
+            lineHeight = TypeScaleTokens.LabelMediumLineHeight,
+            letterSpacing = TypeScaleTokens.LabelMediumTracking
+        )
+    val LabelSmall =
+        TextStyle(
+            fontFamily = TypeScaleTokens.LabelSmallFont,
+            fontWeight = TypeScaleTokens.LabelSmallWeight,
+            fontSize = TypeScaleTokens.LabelSmallSize,
+            lineHeight = TypeScaleTokens.LabelSmallLineHeight,
+            letterSpacing = TypeScaleTokens.LabelSmallTracking
+        )
+    val TitleLarge =
+        TextStyle(
+            fontFamily = TypeScaleTokens.TitleLargeFont,
+            fontWeight = TypeScaleTokens.TitleLargeWeight,
+            fontSize = TypeScaleTokens.TitleLargeSize,
+            lineHeight = TypeScaleTokens.TitleLargeLineHeight,
+            letterSpacing = TypeScaleTokens.TitleLargeTracking
+        )
+    val TitleMedium =
+        TextStyle(
+            fontFamily = TypeScaleTokens.TitleMediumFont,
+            fontWeight = TypeScaleTokens.TitleMediumWeight,
+            fontSize = TypeScaleTokens.TitleMediumSize,
+            lineHeight = TypeScaleTokens.TitleMediumLineHeight,
+            letterSpacing = TypeScaleTokens.TitleMediumTracking
+        )
+    val TitleSmall =
+        TextStyle(
+            fontFamily = TypeScaleTokens.TitleSmallFont,
+            fontWeight = TypeScaleTokens.TitleSmallWeight,
+            fontSize = TypeScaleTokens.TitleSmallSize,
+            lineHeight = TypeScaleTokens.TitleSmallLineHeight,
+            letterSpacing = TypeScaleTokens.TitleSmallTracking
+        )
+}
diff --git a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/HostFragmentBackStackTest.kt b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/HostFragmentBackStackTest.kt
index 8a57fd6..92bed54 100644
--- a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/HostFragmentBackStackTest.kt
+++ b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/HostFragmentBackStackTest.kt
@@ -51,6 +51,7 @@
 class HostFragmentBackStackTest : BaseTest() {
     @Test
     fun test_sameFragment_multipleBackStackEntries() {
+        @Suppress("DEPRECATION")
         FragmentManager.enableDebugLogging(true)
         val containerId = ViewCompat.generateViewId()
         setUpTest(ORIENTATION_HORIZONTAL).apply {
diff --git a/wear/compose/compose-foundation/api/current.txt b/wear/compose/compose-foundation/api/current.txt
index 60ca06a..9f9bb95 100644
--- a/wear/compose/compose-foundation/api/current.txt
+++ b/wear/compose/compose-foundation/api/current.txt
@@ -182,3 +182,145 @@
 
 }
 
+package androidx.wear.compose.foundation.lazy {
+
+  @androidx.compose.runtime.Immutable public final class AutoCenteringParams {
+    ctor public AutoCenteringParams(optional int itemIndex, optional int itemOffset);
+  }
+
+  public final class ScalingLazyColumnDefaults {
+    method public androidx.wear.compose.foundation.lazy.ScalingParams scalingParams(optional float edgeScale, optional float edgeAlpha, optional float minElementHeight, optional float maxElementHeight, optional float minTransitionArea, optional float maxTransitionArea, optional androidx.compose.animation.core.Easing scaleInterpolator, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Constraints,java.lang.Integer> viewportVerticalOffsetResolver);
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.FlingBehavior snapFlingBehavior(androidx.wear.compose.foundation.lazy.ScalingLazyListState state, optional float snapOffset, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decay);
+    field public static final androidx.wear.compose.foundation.lazy.ScalingLazyColumnDefaults INSTANCE;
+  }
+
+  public final class ScalingLazyColumnKt {
+    method @androidx.compose.runtime.Composable public static void ScalingLazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.foundation.lazy.ScalingLazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.foundation.lazy.ScalingParams scalingParams, optional int anchorType, optional androidx.wear.compose.foundation.lazy.AutoCenteringParams? autoCentering, kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit> content);
+    method public static inline <T> void items(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.foundation.lazy.ScalingLazyListItemScope,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void items(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, T![] items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.foundation.lazy.ScalingLazyListItemScope,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void itemsIndexed(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.wear.compose.foundation.lazy.ScalingLazyListItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void itemsIndexed(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, T![] items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.wear.compose.foundation.lazy.ScalingLazyListItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+  }
+
+  public final class ScalingLazyColumnMeasureKt {
+  }
+
+  @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class ScalingLazyListAnchorType {
+    field public static final androidx.wear.compose.foundation.lazy.ScalingLazyListAnchorType.Companion Companion;
+  }
+
+  public static final class ScalingLazyListAnchorType.Companion {
+    method public int getItemCenter();
+    method public int getItemStart();
+    property public final int ItemCenter;
+    property public final int ItemStart;
+  }
+
+  public sealed interface ScalingLazyListItemInfo {
+    method public float getAlpha();
+    method public int getIndex();
+    method public Object getKey();
+    method public int getOffset();
+    method public float getScale();
+    method public int getSize();
+    method public int getUnadjustedOffset();
+    method public int getUnadjustedSize();
+    property public abstract float alpha;
+    property public abstract int index;
+    property public abstract Object key;
+    property public abstract int offset;
+    property public abstract float scale;
+    property public abstract int size;
+    property public abstract int unadjustedOffset;
+    property public abstract int unadjustedSize;
+  }
+
+  @androidx.compose.runtime.Stable @androidx.wear.compose.foundation.lazy.ScalingLazyScopeMarker public sealed interface ScalingLazyListItemScope {
+    method public androidx.compose.ui.Modifier fillParentMaxHeight(androidx.compose.ui.Modifier, optional float fraction);
+    method public androidx.compose.ui.Modifier fillParentMaxSize(androidx.compose.ui.Modifier, optional float fraction);
+    method public androidx.compose.ui.Modifier fillParentMaxWidth(androidx.compose.ui.Modifier, optional float fraction);
+  }
+
+  public sealed interface ScalingLazyListLayoutInfo {
+    method public int getAfterAutoCenteringPadding();
+    method public int getAfterContentPadding();
+    method public int getAnchorType();
+    method public int getBeforeAutoCenteringPadding();
+    method public int getBeforeContentPadding();
+    method public androidx.compose.foundation.gestures.Orientation getOrientation();
+    method public boolean getReverseLayout();
+    method public int getTotalItemsCount();
+    method public int getViewportEndOffset();
+    method public long getViewportSize();
+    method public int getViewportStartOffset();
+    method public java.util.List<androidx.wear.compose.foundation.lazy.ScalingLazyListItemInfo> getVisibleItemsInfo();
+    property public abstract int afterAutoCenteringPadding;
+    property public abstract int afterContentPadding;
+    property public abstract int anchorType;
+    property public abstract int beforeAutoCenteringPadding;
+    property public abstract int beforeContentPadding;
+    property public abstract androidx.compose.foundation.gestures.Orientation orientation;
+    property public abstract boolean reverseLayout;
+    property public abstract int totalItemsCount;
+    property public abstract int viewportEndOffset;
+    property public abstract long viewportSize;
+    property public abstract int viewportStartOffset;
+    property public abstract java.util.List<androidx.wear.compose.foundation.lazy.ScalingLazyListItemInfo> visibleItemsInfo;
+  }
+
+  @androidx.wear.compose.foundation.lazy.ScalingLazyScopeMarker public sealed interface ScalingLazyListScope {
+    method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListItemScope,kotlin.Unit> content);
+    method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.foundation.lazy.ScalingLazyListItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
+  }
+
+  @androidx.compose.runtime.Stable public final class ScalingLazyListState implements androidx.compose.foundation.gestures.ScrollableState {
+    ctor public ScalingLazyListState(optional int initialCenterItemIndex, optional int initialCenterItemScrollOffset);
+    method public suspend Object? animateScrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public float dispatchRawDelta(float delta);
+    method public int getCenterItemIndex();
+    method public int getCenterItemScrollOffset();
+    method public androidx.wear.compose.foundation.lazy.ScalingLazyListLayoutInfo getLayoutInfo();
+    method public boolean isScrollInProgress();
+    method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
+    property public final int centerItemIndex;
+    property public final int centerItemScrollOffset;
+    property public boolean isScrollInProgress;
+    property public final androidx.wear.compose.foundation.lazy.ScalingLazyListLayoutInfo layoutInfo;
+    field public static final androidx.wear.compose.foundation.lazy.ScalingLazyListState.Companion Companion;
+  }
+
+  public static final class ScalingLazyListState.Companion {
+    method public androidx.compose.runtime.saveable.Saver<androidx.wear.compose.foundation.lazy.ScalingLazyListState,java.lang.Object> getSaver();
+    property public final androidx.compose.runtime.saveable.Saver<androidx.wear.compose.foundation.lazy.ScalingLazyListState,java.lang.Object> Saver;
+  }
+
+  public final class ScalingLazyListStateKt {
+    method @androidx.compose.runtime.Composable public static androidx.wear.compose.foundation.lazy.ScalingLazyListState rememberScalingLazyListState(optional int initialCenterItemIndex, optional int initialCenterItemScrollOffset);
+  }
+
+  @kotlin.DslMarker public @interface ScalingLazyScopeMarker {
+  }
+
+  @androidx.compose.runtime.Stable public interface ScalingParams {
+    method public float getEdgeAlpha();
+    method public float getEdgeScale();
+    method public float getMaxElementHeight();
+    method public float getMaxTransitionArea();
+    method public float getMinElementHeight();
+    method public float getMinTransitionArea();
+    method public androidx.compose.animation.core.Easing getScaleInterpolator();
+    method public int resolveViewportVerticalOffset(long viewportConstraints);
+    property public abstract float edgeAlpha;
+    property public abstract float edgeScale;
+    property public abstract float maxElementHeight;
+    property public abstract float maxTransitionArea;
+    property public abstract float minElementHeight;
+    property public abstract float minTransitionArea;
+    property public abstract androidx.compose.animation.core.Easing scaleInterpolator;
+  }
+
+}
+
diff --git a/wear/compose/compose-foundation/api/public_plus_experimental_current.txt b/wear/compose/compose-foundation/api/public_plus_experimental_current.txt
index 60ca06a..9f9bb95 100644
--- a/wear/compose/compose-foundation/api/public_plus_experimental_current.txt
+++ b/wear/compose/compose-foundation/api/public_plus_experimental_current.txt
@@ -182,3 +182,145 @@
 
 }
 
+package androidx.wear.compose.foundation.lazy {
+
+  @androidx.compose.runtime.Immutable public final class AutoCenteringParams {
+    ctor public AutoCenteringParams(optional int itemIndex, optional int itemOffset);
+  }
+
+  public final class ScalingLazyColumnDefaults {
+    method public androidx.wear.compose.foundation.lazy.ScalingParams scalingParams(optional float edgeScale, optional float edgeAlpha, optional float minElementHeight, optional float maxElementHeight, optional float minTransitionArea, optional float maxTransitionArea, optional androidx.compose.animation.core.Easing scaleInterpolator, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Constraints,java.lang.Integer> viewportVerticalOffsetResolver);
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.FlingBehavior snapFlingBehavior(androidx.wear.compose.foundation.lazy.ScalingLazyListState state, optional float snapOffset, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decay);
+    field public static final androidx.wear.compose.foundation.lazy.ScalingLazyColumnDefaults INSTANCE;
+  }
+
+  public final class ScalingLazyColumnKt {
+    method @androidx.compose.runtime.Composable public static void ScalingLazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.foundation.lazy.ScalingLazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.foundation.lazy.ScalingParams scalingParams, optional int anchorType, optional androidx.wear.compose.foundation.lazy.AutoCenteringParams? autoCentering, kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit> content);
+    method public static inline <T> void items(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.foundation.lazy.ScalingLazyListItemScope,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void items(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, T![] items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.foundation.lazy.ScalingLazyListItemScope,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void itemsIndexed(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.wear.compose.foundation.lazy.ScalingLazyListItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void itemsIndexed(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, T![] items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.wear.compose.foundation.lazy.ScalingLazyListItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+  }
+
+  public final class ScalingLazyColumnMeasureKt {
+  }
+
+  @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class ScalingLazyListAnchorType {
+    field public static final androidx.wear.compose.foundation.lazy.ScalingLazyListAnchorType.Companion Companion;
+  }
+
+  public static final class ScalingLazyListAnchorType.Companion {
+    method public int getItemCenter();
+    method public int getItemStart();
+    property public final int ItemCenter;
+    property public final int ItemStart;
+  }
+
+  public sealed interface ScalingLazyListItemInfo {
+    method public float getAlpha();
+    method public int getIndex();
+    method public Object getKey();
+    method public int getOffset();
+    method public float getScale();
+    method public int getSize();
+    method public int getUnadjustedOffset();
+    method public int getUnadjustedSize();
+    property public abstract float alpha;
+    property public abstract int index;
+    property public abstract Object key;
+    property public abstract int offset;
+    property public abstract float scale;
+    property public abstract int size;
+    property public abstract int unadjustedOffset;
+    property public abstract int unadjustedSize;
+  }
+
+  @androidx.compose.runtime.Stable @androidx.wear.compose.foundation.lazy.ScalingLazyScopeMarker public sealed interface ScalingLazyListItemScope {
+    method public androidx.compose.ui.Modifier fillParentMaxHeight(androidx.compose.ui.Modifier, optional float fraction);
+    method public androidx.compose.ui.Modifier fillParentMaxSize(androidx.compose.ui.Modifier, optional float fraction);
+    method public androidx.compose.ui.Modifier fillParentMaxWidth(androidx.compose.ui.Modifier, optional float fraction);
+  }
+
+  public sealed interface ScalingLazyListLayoutInfo {
+    method public int getAfterAutoCenteringPadding();
+    method public int getAfterContentPadding();
+    method public int getAnchorType();
+    method public int getBeforeAutoCenteringPadding();
+    method public int getBeforeContentPadding();
+    method public androidx.compose.foundation.gestures.Orientation getOrientation();
+    method public boolean getReverseLayout();
+    method public int getTotalItemsCount();
+    method public int getViewportEndOffset();
+    method public long getViewportSize();
+    method public int getViewportStartOffset();
+    method public java.util.List<androidx.wear.compose.foundation.lazy.ScalingLazyListItemInfo> getVisibleItemsInfo();
+    property public abstract int afterAutoCenteringPadding;
+    property public abstract int afterContentPadding;
+    property public abstract int anchorType;
+    property public abstract int beforeAutoCenteringPadding;
+    property public abstract int beforeContentPadding;
+    property public abstract androidx.compose.foundation.gestures.Orientation orientation;
+    property public abstract boolean reverseLayout;
+    property public abstract int totalItemsCount;
+    property public abstract int viewportEndOffset;
+    property public abstract long viewportSize;
+    property public abstract int viewportStartOffset;
+    property public abstract java.util.List<androidx.wear.compose.foundation.lazy.ScalingLazyListItemInfo> visibleItemsInfo;
+  }
+
+  @androidx.wear.compose.foundation.lazy.ScalingLazyScopeMarker public sealed interface ScalingLazyListScope {
+    method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListItemScope,kotlin.Unit> content);
+    method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.foundation.lazy.ScalingLazyListItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
+  }
+
+  @androidx.compose.runtime.Stable public final class ScalingLazyListState implements androidx.compose.foundation.gestures.ScrollableState {
+    ctor public ScalingLazyListState(optional int initialCenterItemIndex, optional int initialCenterItemScrollOffset);
+    method public suspend Object? animateScrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public float dispatchRawDelta(float delta);
+    method public int getCenterItemIndex();
+    method public int getCenterItemScrollOffset();
+    method public androidx.wear.compose.foundation.lazy.ScalingLazyListLayoutInfo getLayoutInfo();
+    method public boolean isScrollInProgress();
+    method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
+    property public final int centerItemIndex;
+    property public final int centerItemScrollOffset;
+    property public boolean isScrollInProgress;
+    property public final androidx.wear.compose.foundation.lazy.ScalingLazyListLayoutInfo layoutInfo;
+    field public static final androidx.wear.compose.foundation.lazy.ScalingLazyListState.Companion Companion;
+  }
+
+  public static final class ScalingLazyListState.Companion {
+    method public androidx.compose.runtime.saveable.Saver<androidx.wear.compose.foundation.lazy.ScalingLazyListState,java.lang.Object> getSaver();
+    property public final androidx.compose.runtime.saveable.Saver<androidx.wear.compose.foundation.lazy.ScalingLazyListState,java.lang.Object> Saver;
+  }
+
+  public final class ScalingLazyListStateKt {
+    method @androidx.compose.runtime.Composable public static androidx.wear.compose.foundation.lazy.ScalingLazyListState rememberScalingLazyListState(optional int initialCenterItemIndex, optional int initialCenterItemScrollOffset);
+  }
+
+  @kotlin.DslMarker public @interface ScalingLazyScopeMarker {
+  }
+
+  @androidx.compose.runtime.Stable public interface ScalingParams {
+    method public float getEdgeAlpha();
+    method public float getEdgeScale();
+    method public float getMaxElementHeight();
+    method public float getMaxTransitionArea();
+    method public float getMinElementHeight();
+    method public float getMinTransitionArea();
+    method public androidx.compose.animation.core.Easing getScaleInterpolator();
+    method public int resolveViewportVerticalOffset(long viewportConstraints);
+    property public abstract float edgeAlpha;
+    property public abstract float edgeScale;
+    property public abstract float maxElementHeight;
+    property public abstract float maxTransitionArea;
+    property public abstract float minElementHeight;
+    property public abstract float minTransitionArea;
+    property public abstract androidx.compose.animation.core.Easing scaleInterpolator;
+  }
+
+}
+
diff --git a/wear/compose/compose-foundation/api/restricted_current.txt b/wear/compose/compose-foundation/api/restricted_current.txt
index 60ca06a..9f9bb95 100644
--- a/wear/compose/compose-foundation/api/restricted_current.txt
+++ b/wear/compose/compose-foundation/api/restricted_current.txt
@@ -182,3 +182,145 @@
 
 }
 
+package androidx.wear.compose.foundation.lazy {
+
+  @androidx.compose.runtime.Immutable public final class AutoCenteringParams {
+    ctor public AutoCenteringParams(optional int itemIndex, optional int itemOffset);
+  }
+
+  public final class ScalingLazyColumnDefaults {
+    method public androidx.wear.compose.foundation.lazy.ScalingParams scalingParams(optional float edgeScale, optional float edgeAlpha, optional float minElementHeight, optional float maxElementHeight, optional float minTransitionArea, optional float maxTransitionArea, optional androidx.compose.animation.core.Easing scaleInterpolator, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Constraints,java.lang.Integer> viewportVerticalOffsetResolver);
+    method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.FlingBehavior snapFlingBehavior(androidx.wear.compose.foundation.lazy.ScalingLazyListState state, optional float snapOffset, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decay);
+    field public static final androidx.wear.compose.foundation.lazy.ScalingLazyColumnDefaults INSTANCE;
+  }
+
+  public final class ScalingLazyColumnKt {
+    method @androidx.compose.runtime.Composable public static void ScalingLazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.foundation.lazy.ScalingLazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.foundation.lazy.ScalingParams scalingParams, optional int anchorType, optional androidx.wear.compose.foundation.lazy.AutoCenteringParams? autoCentering, kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit> content);
+    method public static inline <T> void items(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.foundation.lazy.ScalingLazyListItemScope,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void items(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, T![] items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.foundation.lazy.ScalingLazyListItemScope,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void itemsIndexed(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.wear.compose.foundation.lazy.ScalingLazyListItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method public static inline <T> void itemsIndexed(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, T![] items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.wear.compose.foundation.lazy.ScalingLazyListItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+  }
+
+  public final class ScalingLazyColumnMeasureKt {
+  }
+
+  @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class ScalingLazyListAnchorType {
+    field public static final androidx.wear.compose.foundation.lazy.ScalingLazyListAnchorType.Companion Companion;
+  }
+
+  public static final class ScalingLazyListAnchorType.Companion {
+    method public int getItemCenter();
+    method public int getItemStart();
+    property public final int ItemCenter;
+    property public final int ItemStart;
+  }
+
+  public sealed interface ScalingLazyListItemInfo {
+    method public float getAlpha();
+    method public int getIndex();
+    method public Object getKey();
+    method public int getOffset();
+    method public float getScale();
+    method public int getSize();
+    method public int getUnadjustedOffset();
+    method public int getUnadjustedSize();
+    property public abstract float alpha;
+    property public abstract int index;
+    property public abstract Object key;
+    property public abstract int offset;
+    property public abstract float scale;
+    property public abstract int size;
+    property public abstract int unadjustedOffset;
+    property public abstract int unadjustedSize;
+  }
+
+  @androidx.compose.runtime.Stable @androidx.wear.compose.foundation.lazy.ScalingLazyScopeMarker public sealed interface ScalingLazyListItemScope {
+    method public androidx.compose.ui.Modifier fillParentMaxHeight(androidx.compose.ui.Modifier, optional float fraction);
+    method public androidx.compose.ui.Modifier fillParentMaxSize(androidx.compose.ui.Modifier, optional float fraction);
+    method public androidx.compose.ui.Modifier fillParentMaxWidth(androidx.compose.ui.Modifier, optional float fraction);
+  }
+
+  public sealed interface ScalingLazyListLayoutInfo {
+    method public int getAfterAutoCenteringPadding();
+    method public int getAfterContentPadding();
+    method public int getAnchorType();
+    method public int getBeforeAutoCenteringPadding();
+    method public int getBeforeContentPadding();
+    method public androidx.compose.foundation.gestures.Orientation getOrientation();
+    method public boolean getReverseLayout();
+    method public int getTotalItemsCount();
+    method public int getViewportEndOffset();
+    method public long getViewportSize();
+    method public int getViewportStartOffset();
+    method public java.util.List<androidx.wear.compose.foundation.lazy.ScalingLazyListItemInfo> getVisibleItemsInfo();
+    property public abstract int afterAutoCenteringPadding;
+    property public abstract int afterContentPadding;
+    property public abstract int anchorType;
+    property public abstract int beforeAutoCenteringPadding;
+    property public abstract int beforeContentPadding;
+    property public abstract androidx.compose.foundation.gestures.Orientation orientation;
+    property public abstract boolean reverseLayout;
+    property public abstract int totalItemsCount;
+    property public abstract int viewportEndOffset;
+    property public abstract long viewportSize;
+    property public abstract int viewportStartOffset;
+    property public abstract java.util.List<androidx.wear.compose.foundation.lazy.ScalingLazyListItemInfo> visibleItemsInfo;
+  }
+
+  @androidx.wear.compose.foundation.lazy.ScalingLazyScopeMarker public sealed interface ScalingLazyListScope {
+    method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListItemScope,kotlin.Unit> content);
+    method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.foundation.lazy.ScalingLazyListItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
+  }
+
+  @androidx.compose.runtime.Stable public final class ScalingLazyListState implements androidx.compose.foundation.gestures.ScrollableState {
+    ctor public ScalingLazyListState(optional int initialCenterItemIndex, optional int initialCenterItemScrollOffset);
+    method public suspend Object? animateScrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public float dispatchRawDelta(float delta);
+    method public int getCenterItemIndex();
+    method public int getCenterItemScrollOffset();
+    method public androidx.wear.compose.foundation.lazy.ScalingLazyListLayoutInfo getLayoutInfo();
+    method public boolean isScrollInProgress();
+    method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean canScrollBackward;
+    property public boolean canScrollForward;
+    property public final int centerItemIndex;
+    property public final int centerItemScrollOffset;
+    property public boolean isScrollInProgress;
+    property public final androidx.wear.compose.foundation.lazy.ScalingLazyListLayoutInfo layoutInfo;
+    field public static final androidx.wear.compose.foundation.lazy.ScalingLazyListState.Companion Companion;
+  }
+
+  public static final class ScalingLazyListState.Companion {
+    method public androidx.compose.runtime.saveable.Saver<androidx.wear.compose.foundation.lazy.ScalingLazyListState,java.lang.Object> getSaver();
+    property public final androidx.compose.runtime.saveable.Saver<androidx.wear.compose.foundation.lazy.ScalingLazyListState,java.lang.Object> Saver;
+  }
+
+  public final class ScalingLazyListStateKt {
+    method @androidx.compose.runtime.Composable public static androidx.wear.compose.foundation.lazy.ScalingLazyListState rememberScalingLazyListState(optional int initialCenterItemIndex, optional int initialCenterItemScrollOffset);
+  }
+
+  @kotlin.DslMarker public @interface ScalingLazyScopeMarker {
+  }
+
+  @androidx.compose.runtime.Stable public interface ScalingParams {
+    method public float getEdgeAlpha();
+    method public float getEdgeScale();
+    method public float getMaxElementHeight();
+    method public float getMaxTransitionArea();
+    method public float getMinElementHeight();
+    method public float getMinTransitionArea();
+    method public androidx.compose.animation.core.Easing getScaleInterpolator();
+    method public int resolveViewportVerticalOffset(long viewportConstraints);
+    property public abstract float edgeAlpha;
+    property public abstract float edgeScale;
+    property public abstract float maxElementHeight;
+    property public abstract float maxTransitionArea;
+    property public abstract float minElementHeight;
+    property public abstract float minTransitionArea;
+    property public abstract androidx.compose.animation.core.Easing scaleInterpolator;
+  }
+
+}
+
diff --git a/wear/compose/compose-foundation/benchmark/benchmark-proguard-rules.pro b/wear/compose/compose-foundation/benchmark/benchmark-proguard-rules.pro
new file mode 100644
index 0000000..e4061d2
--- /dev/null
+++ b/wear/compose/compose-foundation/benchmark/benchmark-proguard-rules.pro
@@ -0,0 +1,37 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+
+-dontobfuscate
+
+-ignorewarnings
+
+-keepattributes *Annotation*
+
+-dontnote junit.framework.**
+-dontnote junit.runner.**
+
+-dontwarn androidx.test.**
+-dontwarn org.junit.**
+-dontwarn org.hamcrest.**
+-dontwarn com.squareup.javawriter.JavaWriter
+
+-keepclasseswithmembers @org.junit.runner.RunWith public class *
\ No newline at end of file
diff --git a/wear/compose/compose-foundation/benchmark/build.gradle b/wear/compose/compose-foundation/benchmark/build.gradle
new file mode 100644
index 0000000..516348d
--- /dev/null
+++ b/wear/compose/compose-foundation/benchmark/build.gradle
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.build.LibraryType
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("AndroidXComposePlugin")
+    id("org.jetbrains.kotlin.android")
+    id("androidx.benchmark")
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 25
+    }
+    buildTypes.all {
+        consumerProguardFiles "benchmark-proguard-rules.pro"
+    }
+    namespace "androidx.wear.compose.foundation.benchmark"
+}
+
+dependencies {
+
+    androidTestImplementation project(":benchmark:benchmark-junit4")
+    androidTestImplementation project(":compose:runtime:runtime")
+    androidTestImplementation project(":compose:ui:ui-text:ui-text-benchmark")
+    androidTestImplementation project(":compose:foundation:foundation")
+    androidTestImplementation project(":compose:runtime:runtime")
+    androidTestImplementation project(":compose:benchmark-utils")
+    androidTestImplementation project(":wear:compose:compose-foundation")
+    androidTestImplementation(libs.testRules)
+    androidTestImplementation(libs.junit)
+    androidTestImplementation(libs.kotlinStdlib)
+    androidTestImplementation(libs.kotlinReflect)
+    androidTestImplementation(libs.kotlinTestCommon)
+    androidTestImplementation(libs.truth)
+}
+androidx {
+    type = LibraryType.INTERNAL_TEST_LIBRARY
+}
diff --git a/wear/compose/compose-foundation/benchmark/src/androidTest/AndroidManifest.xml b/wear/compose/compose-foundation/benchmark/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..39f7fc3
--- /dev/null
+++ b/wear/compose/compose-foundation/benchmark/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<manifest />
\ No newline at end of file
diff --git a/wear/compose/compose-foundation/benchmark/src/androidTest/java/androidx/wear/compose/foundation/benchmark/ScalingLazyColumnBenchmark.kt b/wear/compose/compose-foundation/benchmark/src/androidTest/java/androidx/wear/compose/foundation/benchmark/ScalingLazyColumnBenchmark.kt
new file mode 100644
index 0000000..c7fc419
--- /dev/null
+++ b/wear/compose/compose-foundation/benchmark/src/androidTest/java/androidx/wear/compose/foundation/benchmark/ScalingLazyColumnBenchmark.kt
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.foundation.benchmark
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.ComposeTestCase
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.assertNoPendingChanges
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.benchmarkDrawPerf
+import androidx.compose.testutils.benchmark.benchmarkFirstCompose
+import androidx.compose.testutils.benchmark.benchmarkLayoutPerf
+import androidx.compose.testutils.benchmark.recomposeUntilNoChangesPending
+import androidx.compose.testutils.doFramesUntilNoChangesPending
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Benchmark for Wear Compose ScalingLazyColumn.
+ */
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class ScalingLazyColumnBenchmark {
+
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    private val scalingLazyColumnCaseFactory = { ScalingLazyColumnTestCase() }
+
+    @Test
+    fun first_compose() {
+        benchmarkRule.benchmarkFirstCompose(scalingLazyColumnCaseFactory)
+    }
+
+    @Test
+    fun first_measure() {
+        benchmarkRule.benchmarkFirstScalingLazyColumnMeasure(scalingLazyColumnCaseFactory)
+    }
+
+    @Test
+    fun first_layout() {
+        benchmarkRule.benchmarkFirstScalingLazyColumnLayout(scalingLazyColumnCaseFactory)
+    }
+
+    @Test
+    fun first_draw() {
+        benchmarkRule.benchmarkFirstScalingLazyColumnDraw(scalingLazyColumnCaseFactory)
+    }
+
+    @Test
+    fun layout() {
+        benchmarkRule.benchmarkLayoutPerf(scalingLazyColumnCaseFactory)
+    }
+
+    @Test
+    fun draw() {
+        benchmarkRule.benchmarkDrawPerf(scalingLazyColumnCaseFactory)
+    }
+}
+
+internal class ScalingLazyColumnTestCase : LayeredComposeTestCase() {
+    private var itemSizeDp: Dp = 10.dp
+    private var defaultItemSpacingDp: Dp = 4.dp
+
+    @Composable
+    override fun MeasuredContent() {
+        ScalingLazyColumn(
+            state = rememberScalingLazyListState(),
+            modifier = Modifier.requiredSize(
+                itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+            ),
+        ) {
+            items(10) { it ->
+                Box(Modifier.requiredSize(itemSizeDp)) {
+                    BasicText(text = "Item $it",
+                        Modifier
+                            .background(Color.White)
+                            .padding(2.dp),
+                        TextStyle(
+                            color = Color.Black,
+                            fontSize = 16.sp,
+                        )
+                    )
+                }
+            }
+        }
+    }
+}
+
+// TODO (b/210654937): Should be able to get rid of this workaround in the future once able to call
+// LaunchedEffect directly on underlying LazyColumn rather than via a 2-stage initialization via
+// onGloballyPositioned().
+fun ComposeBenchmarkRule.benchmarkFirstScalingLazyColumnMeasure(
+    caseFactory: () -> LayeredComposeTestCase
+) {
+    runBenchmarkFor(LayeredCaseAdapter.of(caseFactory)) {
+        measureRepeated {
+            runWithTimingDisabled {
+                doFramesUntilNoChangesPending()
+                // Add the content to benchmark
+                getTestCase().addMeasuredContent()
+                recomposeUntilNoChangesPending()
+                requestLayout()
+            }
+
+            measure()
+            recomposeUntilNoChangesPending()
+
+            runWithTimingDisabled {
+                assertNoPendingChanges()
+                disposeContent()
+            }
+        }
+    }
+}
+
+// TODO (b/210654937): Should be able to get rid of this workaround in the future once able to call
+// LaunchedEffect directly on underlying LazyColumn rather than via a 2-stage initialization via
+// onGloballyPositioned().
+fun ComposeBenchmarkRule.benchmarkFirstScalingLazyColumnLayout(
+    caseFactory: () -> LayeredComposeTestCase
+) {
+    runBenchmarkFor(LayeredCaseAdapter.of(caseFactory)) {
+        measureRepeated {
+            runWithTimingDisabled {
+                doFramesUntilNoChangesPending()
+                // Add the content to benchmark
+                getTestCase().addMeasuredContent()
+                recomposeUntilNoChangesPending()
+                requestLayout()
+                measure()
+            }
+
+            layout()
+            recomposeUntilNoChangesPending()
+
+            runWithTimingDisabled {
+                assertNoPendingChanges()
+                disposeContent()
+            }
+        }
+    }
+}
+
+// TODO (b/210654937): Should be able to get rid of this workaround in the future once able to call
+// LaunchedEffect directly on underlying LazyColumn rather than via a 2-stage initialization via
+// onGloballyPositioned().
+fun ComposeBenchmarkRule.benchmarkFirstScalingLazyColumnDraw(
+    caseFactory: () -> LayeredComposeTestCase
+) {
+    runBenchmarkFor(LayeredCaseAdapter.of(caseFactory)) {
+        measureRepeated {
+            runWithTimingDisabled {
+                doFramesUntilNoChangesPending()
+                // Add the content to benchmark
+                getTestCase().addMeasuredContent()
+                recomposeUntilNoChangesPending()
+                requestLayout()
+                measure()
+                layout()
+                drawPrepare()
+            }
+
+            draw()
+            drawFinish()
+            recomposeUntilNoChangesPending()
+
+            runWithTimingDisabled {
+                assertNoPendingChanges()
+                disposeContent()
+            }
+        }
+    }
+}
+
+private class LayeredCaseAdapter(private val innerCase: LayeredComposeTestCase) : ComposeTestCase {
+
+    companion object {
+        fun of(caseFactory: () -> LayeredComposeTestCase): () -> LayeredCaseAdapter = {
+            LayeredCaseAdapter(caseFactory())
+        }
+    }
+
+    var isComposed by mutableStateOf(false)
+
+    @Composable
+    override fun Content() {
+        innerCase.ContentWrappers {
+            if (isComposed) {
+                innerCase.MeasuredContent()
+            }
+        }
+    }
+
+    fun addMeasuredContent() {
+        Assert.assertTrue(!isComposed)
+        isComposed = true
+    }
+}
\ No newline at end of file
diff --git a/wear/compose/compose-foundation/benchmark/src/main/AndroidManifest.xml b/wear/compose/compose-foundation/benchmark/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..490f800
--- /dev/null
+++ b/wear/compose/compose-foundation/benchmark/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+    <application/>
+</manifest>
\ No newline at end of file
diff --git a/wear/compose/compose-foundation/build.gradle b/wear/compose/compose-foundation/build.gradle
index a2ff8da..5a85f33 100644
--- a/wear/compose/compose-foundation/build.gradle
+++ b/wear/compose/compose-foundation/build.gradle
@@ -46,6 +46,7 @@
     androidTestImplementation(project(":compose:test-utils"))
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.kotlinTest)
+    androidTestImplementation(libs.truth)
 
     samples(project(":wear:compose:compose-foundation-samples"))
 }
diff --git a/wear/compose/compose-foundation/samples/build.gradle b/wear/compose/compose-foundation/samples/build.gradle
index 5cd0e39..e1bc9aa 100644
--- a/wear/compose/compose-foundation/samples/build.gradle
+++ b/wear/compose/compose-foundation/samples/build.gradle
@@ -34,6 +34,7 @@
     implementation(project(":compose:ui:ui"))
     implementation(project(":compose:ui:ui-text"))
     implementation(project(":wear:compose:compose-foundation"))
+    implementation(project(":wear:compose:compose-material"))
 }
 
 android {
diff --git a/wear/compose/compose-foundation/samples/src/main/java/androidx/wear/compose/foundation/samples/ScalingLazyColumnSample.kt b/wear/compose/compose-foundation/samples/src/main/java/androidx/wear/compose/foundation/samples/ScalingLazyColumnSample.kt
new file mode 100644
index 0000000..b6b06ce
--- /dev/null
+++ b/wear/compose/compose-foundation/samples/src/main/java/androidx/wear/compose/foundation/samples/ScalingLazyColumnSample.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.foundation.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.lazy.AutoCenteringParams
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumnDefaults
+import androidx.wear.compose.foundation.lazy.ScalingLazyListAnchorType
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
+import androidx.wear.compose.material.Chip
+import androidx.wear.compose.material.ChipDefaults
+import androidx.wear.compose.material.ListHeader
+import androidx.wear.compose.material.Text
+import kotlinx.coroutines.launch
+
+@Sampled
+@Composable
+fun SimpleScalingLazyColumn() {
+    ScalingLazyColumn(
+        modifier = Modifier.fillMaxWidth()
+    ) {
+        item {
+            ListHeader {
+                Text(text = "List Header")
+            }
+        }
+        items(20) {
+            Chip(
+                onClick = { },
+                label = { Text("List item $it") },
+                colors = ChipDefaults.secondaryChipColors()
+            )
+        }
+    }
+}
+
+@Sampled
+@Composable
+fun SimpleScalingLazyColumnWithSnap() {
+    val state = rememberScalingLazyListState()
+    ScalingLazyColumn(
+        modifier = Modifier.fillMaxWidth(),
+        state = state,
+        flingBehavior = ScalingLazyColumnDefaults.snapFlingBehavior(state = state)
+    ) {
+        item {
+            ListHeader {
+                Text(text = "List Header")
+            }
+        }
+        items(20) {
+            Chip(
+                onClick = { },
+                label = { Text("List item $it") },
+                colors = ChipDefaults.secondaryChipColors()
+            )
+        }
+    }
+}
+
+@Sampled
+@Composable
+fun ScalingLazyColumnEdgeAnchoredAndAnimatedScrollTo() {
+    val coroutineScope = rememberCoroutineScope()
+    val itemSpacing = 6.dp
+    // Line up the gap between the items on the center-line
+    val scrollOffset = with(LocalDensity.current) {
+        -(itemSpacing / 2).roundToPx()
+    }
+    val state = rememberScalingLazyListState(
+        initialCenterItemIndex = 1,
+        initialCenterItemScrollOffset = scrollOffset
+    )
+
+    ScalingLazyColumn(
+        modifier = Modifier.fillMaxWidth(),
+        anchorType = ScalingLazyListAnchorType.ItemStart,
+        verticalArrangement = Arrangement.spacedBy(itemSpacing),
+        state = state,
+        autoCentering = AutoCenteringParams(itemOffset = scrollOffset)
+    ) {
+        item {
+            ListHeader {
+                Text(text = "List Header")
+            }
+        }
+        items(20) {
+            Chip(
+                onClick = {
+                    coroutineScope.launch {
+                        // Add +1 to allow for the ListHeader
+                        state.animateScrollToItem(it + 1, scrollOffset)
+                    }
+                },
+                label = { Text("List item $it") },
+                colors = ChipDefaults.secondaryChipColors()
+            )
+        }
+    }
+}
+
+@Sampled
+@Composable
+fun SimpleScalingLazyColumnWithContentPadding() {
+    ScalingLazyColumn(
+        modifier = Modifier.fillMaxWidth(),
+        contentPadding = PaddingValues(top = 20.dp, bottom = 20.dp),
+        autoCentering = null
+    ) {
+        item {
+            ListHeader {
+                Text(text = "List Header")
+            }
+        }
+        items(20) {
+            Chip(
+                onClick = { },
+                label = { Text("List item $it") },
+                colors = ChipDefaults.secondaryChipColors()
+            )
+        }
+    }
+}
diff --git a/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/CurvedLayoutTest.kt b/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/CurvedLayoutTest.kt
index 34fa6cf..02cf6cc 100644
--- a/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/CurvedLayoutTest.kt
+++ b/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/CurvedLayoutTest.kt
@@ -414,8 +414,6 @@
     }
 }
 
-internal const val TEST_TAG = "test-item"
-
 fun checkAngle(expected: Float, actual: Float) {
     var d = abs(expected - actual)
     d = min(d, 360 - d)
diff --git a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt b/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/FoundationTest.kt
similarity index 72%
copy from tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
copy to wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/FoundationTest.kt
index ebac64e..4590238 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
+++ b/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/FoundationTest.kt
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-package androidx.tv.material
+package androidx.wear.compose.foundation
 
-@RequiresOptIn(
-    "This tv-material API is experimental and likely to change or be removed in the future."
-)
-@Retention(AnnotationRetention.BINARY)
-annotation class ExperimentalTvMaterialApi
\ No newline at end of file
+internal const val TEST_TAG = "test-item"
diff --git a/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyColumnIndexedTest.kt b/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyColumnIndexedTest.kt
new file mode 100644
index 0000000..9246678
--- /dev/null
+++ b/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyColumnIndexedTest.kt
@@ -0,0 +1,157 @@
+/*
+ * 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.foundation.lazy
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.unit.dp
+import org.junit.Rule
+import org.junit.Test
+
+class ScalingLazyColumnIndexedTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun scalingLazyColumnShowsIndexedItems() {
+        lateinit var state: ScalingLazyListState
+        val items = (1..4).map { it.toString() }
+        val viewPortHeight = 100.dp
+        val itemHeight = 51.dp
+        val itemWidth = 50.dp
+        val gapBetweenItems = 2.dp
+
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(initialCenterItemIndex = 0)
+                    .also { state = it },
+                modifier = Modifier.height(viewPortHeight),
+                verticalArrangement = Arrangement.spacedBy(gapBetweenItems),
+                scalingParams = ScalingLazyColumnDefaults.scalingParams(
+                    edgeScale = 1.0f,
+                    // Create some extra composables to check that extraPadding works.
+                    viewportVerticalOffsetResolver = { (it.maxHeight / 10f).toInt() }
+                )
+            ) {
+                itemsIndexed(items) { index, item ->
+                    Spacer(
+                        Modifier.height(itemHeight).width(itemWidth)
+                            .testTag("$index-$item")
+                    )
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        // Fully visible
+        rule.onNodeWithTag("0-1")
+            .assertIsDisplayed()
+
+        // Partially visible
+        rule.onNodeWithTag("1-2")
+            .assertIsDisplayed()
+
+        // Will have been composed but should not be visible
+        rule.onNodeWithTag("2-3")
+            .assertIsNotDisplayed()
+
+        // Should not have been composed
+        rule.onNodeWithTag("3-4")
+            .assertDoesNotExist()
+    }
+
+    @Test
+    fun columnWithIndexesComposedWithCorrectIndexAndItem() {
+        lateinit var state: ScalingLazyListState
+        val items = (0..1).map { it.toString() }
+
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(initialCenterItemIndex = 0)
+                    .also { state = it },
+                modifier = Modifier.height(200.dp),
+                autoCentering = null,
+                scalingParams = ScalingLazyColumnDefaults.scalingParams(edgeScale = 1.0f)
+            ) {
+                itemsIndexed(items) { index, item ->
+                    BasicText(
+                        "${index}x$item", Modifier.requiredHeight(100.dp)
+                    )
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.onNodeWithText("0x0")
+            .assertTopPositionInRootIsEqualTo(0.dp)
+
+        rule.onNodeWithText("1x1")
+            .assertTopPositionInRootIsEqualTo(104.dp)
+    }
+
+    @Test
+    fun columnWithIndexesComposedWithCorrectIndexAndItemWithAutoCentering() {
+        lateinit var state: ScalingLazyListState
+        val items = (0..1).map { it.toString() }
+        val viewPortHeight = 100.dp
+        val itemHeight = 50.dp
+        val gapBetweenItems = 2.dp
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(initialCenterItemIndex = 0)
+                    .also { state = it },
+                modifier = Modifier.height(viewPortHeight),
+                autoCentering = AutoCenteringParams(itemIndex = 0),
+                verticalArrangement = Arrangement.spacedBy(gapBetweenItems),
+                // No scaling as we are doing maths with expected item sizes
+                scalingParams = ScalingLazyColumnDefaults.scalingParams(edgeScale = 1.0f)
+            ) {
+                itemsIndexed(items) { index, item ->
+                    BasicText(
+                        "${index}x$item", Modifier.requiredHeight(itemHeight)
+                    )
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        // Check that first item is in the center of the viewport
+        val firstItemStart = viewPortHeight / 2f - itemHeight / 2f
+        rule.onNodeWithText("0x0")
+            .assertTopPositionInRootIsEqualTo(firstItemStart)
+
+        // And that the second item is item height + gap between items below it
+        rule.onNodeWithText("1x1")
+            .assertTopPositionInRootIsEqualTo(firstItemStart + itemHeight + gapBetweenItems)
+    }
+}
diff --git a/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyColumnTest.kt b/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyColumnTest.kt
new file mode 100644
index 0000000..9eb2282
--- /dev/null
+++ b/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyColumnTest.kt
@@ -0,0 +1,977 @@
+/*
+ * 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.foundation.lazy
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.layout.requiredWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.WithTouchSlop
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeDown
+import androidx.compose.ui.test.swipeUp
+import androidx.compose.ui.test.swipeWithVelocity
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.wear.compose.foundation.TEST_TAG
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import com.google.common.truth.Truth.assertThat
+import kotlin.math.roundToInt
+import kotlinx.coroutines.runBlocking
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+// These tests are in addition to ScalingLazyListLayoutInfoTest which handles scroll events at an
+// absolute level and is designed to exercise scrolling through the UI directly.
+public class ScalingLazyColumnTest {
+    private val scalingLazyColumnTag = "scalingLazyColumnTag"
+    private val firstItemTag = "firstItemTag"
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private var itemSizePx: Int = 50
+    private var itemSizeDp: Dp = Dp.Infinity
+    private var defaultItemSpacingDp: Dp = 4.dp
+    private var defaultItemSpacingPx = Int.MAX_VALUE
+
+    @Before
+    fun before() {
+        with(rule.density) {
+            itemSizeDp = itemSizePx.toDp()
+            defaultItemSpacingPx = defaultItemSpacingDp.roundToPx()
+        }
+    }
+
+    @Test
+    fun initializationCorrectWithAutoCenteringAndNormalItemPaddingForZeroItemList() {
+        initializationCorrectWithAutoCenteringAndNormalItemPaddingForNItemList(0)
+    }
+
+    @Test
+    fun initializationCorrectWithAutoCenteringAndNormalItemPaddingForOneItemList() {
+        initializationCorrectWithAutoCenteringAndNormalItemPaddingForNItemList(1)
+    }
+
+    @Test
+    fun initializationCorrectWithAutoCenteringAndNormalItemPaddingForTwoItemList() {
+        initializationCorrectWithAutoCenteringAndNormalItemPaddingForNItemList(2)
+    }
+
+    @Test
+    fun initializationCorrectWithAutoCenteringAndNormalItemPaddingForThreeItemList() {
+        initializationCorrectWithAutoCenteringAndNormalItemPaddingForNItemList(3)
+    }
+
+    @Test
+    fun initializationCorrectWithAutoCenteringAndNormalItemPaddingForFourItemList() {
+        initializationCorrectWithAutoCenteringAndNormalItemPaddingForNItemList(4)
+    }
+
+    @Test
+    fun initializationCorrectWithAutoCenteringAndNormalItemPaddingForFiveItemList() {
+        initializationCorrectWithAutoCenteringAndNormalItemPaddingForNItemList(5)
+    }
+
+    private fun initializationCorrectWithAutoCenteringAndNormalItemPaddingForNItemList(
+        itemCount: Int
+    ) {
+        lateinit var state: ScalingLazyListState
+        val listSize = itemSizeDp * 3.5f
+        rule.setContent {
+            WithTouchSlop(0f) {
+                ScalingLazyColumn(
+                    state = rememberScalingLazyListState().also { state = it },
+                    modifier = Modifier.testTag(TEST_TAG).requiredSize(listSize),
+                ) {
+                    items(itemCount) {
+                        Box(Modifier.requiredSize(itemSizeDp))
+                    }
+                }
+            }
+        }
+
+        rule.waitUntil { state.initialized.value }
+    }
+
+    @Test
+    fun initializationCorrectWithAutoCenteringAndCustomInterItemPadding() {
+        lateinit var state: ScalingLazyListState
+        val listSize = itemSizeDp * 3.5f
+        rule.setContent {
+            WithTouchSlop(0f) {
+                ScalingLazyColumn(
+                    state = rememberScalingLazyListState().also { state = it },
+                    modifier = Modifier.testTag(TEST_TAG).requiredSize(listSize),
+                    scalingParams = ScalingLazyColumnDefaults.scalingParams(edgeScale = 1f),
+                    // We want to arrange for the autoCentering spacer to be of size zero, but
+                    // also for the gap between items to be needed.
+                    verticalArrangement = Arrangement.spacedBy(itemSizeDp * 0.25f * 0.75f),
+                ) {
+                    items(5) {
+                        Box(Modifier.requiredSize(itemSizeDp))
+                    }
+                }
+            }
+        }
+
+        rule.waitUntil { state.initialized.value }
+    }
+
+    @Test
+    fun initializationCorrectWithAutoCenteringAndLargeFirstItem() {
+        lateinit var state: ScalingLazyListState
+        val listSize = itemSizeDp * 3.5f
+        rule.setContent {
+            WithTouchSlop(0f) {
+                ScalingLazyColumn(
+                    state = rememberScalingLazyListState().also { state = it },
+                    modifier = Modifier.testTag(TEST_TAG).requiredSize(listSize),
+                    scalingParams = ScalingLazyColumnDefaults.scalingParams(edgeScale = 1f),
+                ) {
+                    item {
+                        Box(Modifier.requiredSize(itemSizeDp * 2))
+                    }
+                    items(5) {
+                        Box(Modifier.requiredSize(itemSizeDp))
+                    }
+                }
+            }
+        }
+
+        rule.waitUntil { state.initialized.value }
+    }
+
+    @Test
+    fun autoCenteringCorrectSizeWithCenterAnchor() {
+        lateinit var state: ScalingLazyListState
+        val listSize = itemSizeDp * 3.5f
+        rule.setContent {
+            WithTouchSlop(0f) {
+                ScalingLazyColumn(
+                    state = rememberScalingLazyListState().also { state = it },
+                    modifier = Modifier.testTag(TEST_TAG).requiredSize(listSize),
+                    anchorType = ScalingLazyListAnchorType.ItemCenter,
+                    autoCentering = AutoCenteringParams(),
+                    verticalArrangement = Arrangement.spacedBy(0.dp),
+                    scalingParams = ScalingLazyColumnDefaults.scalingParams(
+                        edgeScale = 0f,
+                        minTransitionArea = 0.5f,
+                        maxTransitionArea = 0.5f
+                    )
+                ) {
+                    items(5) {
+                        Box(Modifier.requiredSize(itemSizeDp))
+                    }
+                }
+            }
+        }
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.waitForIdle()
+
+        val listSizePx = with(rule.density) {
+            listSize.roundToPx()
+        }
+        rule.runOnIdle {
+            // Make sure that the edge items have been scaled
+            assertThat(state.layoutInfo.visibleItemsInfo.first().scale).isLessThan(1.0f)
+            // But that size of the Spacer is as expected - it should be half the viewport size
+            // rounded down minus half the size of the center item rounded down minus the full size
+            // of the 0th item
+            assertThat(state.lazyListState.layoutInfo.visibleItemsInfo.first().size)
+                .isEqualTo((listSizePx / 2) - (itemSizePx + itemSizePx / 2))
+        }
+        rule.onNodeWithTag(TEST_TAG).performTouchInput {
+            swipeUp()
+        }
+        rule.runOnIdle {
+            // Check that the last item has been scrolled into view
+            assertThat(state.lazyListState.layoutInfo.visibleItemsInfo.last().index)
+                .isEqualTo(state.lazyListState.layoutInfo.totalItemsCount - 1)
+            // And that size of the Spacer is as expected
+            assertThat(state.lazyListState.layoutInfo.visibleItemsInfo.last().size)
+                .isEqualTo(((listSizePx / 2f) - (itemSizePx / 2f)).roundToInt())
+        }
+    }
+
+    @Test
+    fun autoCenteringCorrectSizeWithStartAnchor() {
+        lateinit var state: ScalingLazyListState
+        val listSize = itemSizeDp * 3.5f
+        rule.setContent {
+            WithTouchSlop(0f) {
+                ScalingLazyColumn(
+                    state = rememberScalingLazyListState().also { state = it },
+                    modifier = Modifier.testTag(TEST_TAG).requiredSize(listSize),
+                    anchorType = ScalingLazyListAnchorType.ItemStart,
+                    autoCentering = AutoCenteringParams(),
+                    verticalArrangement = Arrangement.spacedBy(0.dp),
+                    scalingParams = ScalingLazyColumnDefaults.scalingParams(
+                        edgeScale = 0f,
+                        minTransitionArea = 0.5f,
+                        maxTransitionArea = 0.5f
+                    )
+                ) {
+                    items(5) {
+                        Box(Modifier.requiredSize(itemSizeDp))
+                    }
+                }
+            }
+        }
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.waitForIdle()
+
+        val listSizePx = with(rule.density) {
+            listSize.roundToPx()
+        }
+
+        rule.runOnIdle {
+            // Make sure that the edge items have been scaled
+            assertThat(state.layoutInfo.visibleItemsInfo.first().scale).isLessThan(1.0f)
+            // But that size of the Spacer is as expected, it should be half the viewport size
+            // rounded down minus the size of zeroth item in the list
+            assertThat(state.lazyListState.layoutInfo.visibleItemsInfo.first().size)
+                .isEqualTo((listSizePx / 2) - itemSizePx)
+        }
+        rule.onNodeWithTag(TEST_TAG).performTouchInput {
+            swipeUp(endY = top)
+        }
+        rule.runOnIdle {
+            // Check that the last item has been scrolled into view
+            assertThat(state.lazyListState.layoutInfo.visibleItemsInfo.last().index)
+                .isEqualTo(state.lazyListState.layoutInfo.totalItemsCount - 1)
+            // And that size of the Spacer is as expected
+            assertThat(state.lazyListState.layoutInfo.visibleItemsInfo.last().size)
+                .isEqualTo(((listSizePx / 2f) - itemSizePx).roundToInt())
+        }
+    }
+
+    @Test
+    fun visibleItemsAreCorrectAfterScrolling() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            WithTouchSlop(0f) {
+                ScalingLazyColumn(
+                    state = rememberScalingLazyListState().also { state = it },
+                    modifier = Modifier.testTag(TEST_TAG).requiredSize(
+                        itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                    ),
+                    autoCentering = null
+                ) {
+                    items(5) {
+                        Box(Modifier.requiredSize(itemSizeDp))
+                    }
+                }
+            }
+        }
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.waitForIdle()
+
+        state.layoutInfo.assertVisibleItems(count = 4, startIndex = 0)
+
+        rule.onNodeWithTag(TEST_TAG).performTouchInput {
+            swipeUp(endY = bottom - (itemSizePx.toFloat() + defaultItemSpacingPx.toFloat()))
+        }
+
+        rule.waitForIdle()
+        state.layoutInfo.assertVisibleItems(count = 4, startIndex = 1)
+    }
+
+    @Test
+    fun visibleItemsAreCorrectAfterAttemptedScrollWithUserScrollDisabled() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            WithTouchSlop(0f) {
+                ScalingLazyColumn(
+                    state = rememberScalingLazyListState().also { state = it },
+                    modifier = Modifier.testTag(TEST_TAG).requiredSize(
+                        itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                    ),
+                    autoCentering = null,
+                    userScrollEnabled = false
+                ) {
+                    items(5) {
+                        Box(Modifier.requiredSize(itemSizeDp))
+                    }
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.waitForIdle()
+
+        state.layoutInfo.assertVisibleItems(count = 4, startIndex = 0)
+
+        rule.onNodeWithTag(TEST_TAG).performTouchInput {
+            swipeUp(endY = bottom - (itemSizePx.toFloat() + defaultItemSpacingPx.toFloat()))
+        }
+
+        rule.waitForIdle()
+        state.layoutInfo.assertVisibleItems(count = 4, startIndex = 0)
+    }
+
+    @Test
+    fun visibleItemsAreCorrectAfterScrollingWithAutoCentering() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            WithTouchSlop(0f) {
+                ScalingLazyColumn(
+                    state = rememberScalingLazyListState(initialCenterItemIndex = 0)
+                        .also { state = it },
+                    modifier = Modifier.testTag(TEST_TAG).requiredSize(
+                        itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                    ),
+                    autoCentering = AutoCenteringParams(itemIndex = 0)
+                ) {
+                    items(5) {
+                        Box(Modifier.requiredSize(itemSizeDp))
+                    }
+                }
+            }
+        }
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.waitForIdle()
+        state.layoutInfo.assertVisibleItems(count = 3, startIndex = 0)
+
+        rule.waitForIdle()
+        rule.onNodeWithTag(TEST_TAG).performTouchInput {
+            swipeUp(endY = bottom - (itemSizePx.toFloat() + defaultItemSpacingPx.toFloat()))
+        }
+
+        rule.waitForIdle()
+        state.layoutInfo.assertVisibleItems(count = 4, startIndex = 0)
+    }
+
+    @Test
+    fun visibleItemsAreCorrectAfterScrollingWithSnap() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            WithTouchSlop(0f) {
+                ScalingLazyColumn(
+                    state = rememberScalingLazyListState(initialCenterItemIndex = 0)
+                        .also { state = it },
+                    modifier = Modifier.testTag(TEST_TAG).requiredSize(
+                        itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                    ),
+                    autoCentering = AutoCenteringParams(itemIndex = 0),
+                    flingBehavior = ScalingLazyColumnDefaults.snapFlingBehavior(state)
+                ) {
+                    items(5) {
+                        Box(Modifier.requiredSize(itemSizeDp))
+                    }
+                }
+            }
+        }
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.waitForIdle()
+
+        state.layoutInfo.assertVisibleItems(count = 3, startIndex = 0)
+
+        rule.waitForIdle()
+        rule.onNodeWithTag(TEST_TAG).performTouchInput {
+            // Swipe by an amount that is not a whole item + gap
+            swipeWithVelocity(
+                start = Offset(centerX, bottom),
+                end = Offset(centerX, bottom - (itemSizePx.toFloat())),
+                endVelocity = 1f, // Ensure it's not a fling.
+            )
+        }
+
+        rule.waitForIdle()
+        state.layoutInfo.assertVisibleItems(count = 4, startIndex = 0)
+        assertThat(state.centerItemIndex).isEqualTo(1)
+        assertThat(state.centerItemScrollOffset).isEqualTo(0)
+    }
+
+    @Test
+    fun visibleItemsAreCorrectAfterScrollingWithSnapAndOffset() {
+        lateinit var state: ScalingLazyListState
+        val snapOffset = 5.dp
+        var snapOffsetPx = 0
+        rule.setContent {
+            WithTouchSlop(0f) {
+                snapOffsetPx = with(LocalDensity.current) { snapOffset.roundToPx() }
+
+                ScalingLazyColumn(
+                    state = rememberScalingLazyListState(initialCenterItemIndex = 0)
+                        .also { state = it },
+                    modifier = Modifier.testTag(TEST_TAG).requiredSize(
+                        itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                    ),
+                    autoCentering = AutoCenteringParams(itemIndex = 0),
+                    flingBehavior = ScalingLazyColumnDefaults.snapFlingBehavior(
+                        state = state,
+                        snapOffset = snapOffset
+                    )
+                ) {
+                    items(5) {
+                        Box(Modifier.requiredSize(itemSizeDp))
+                    }
+                }
+            }
+        }
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.waitForIdle()
+        state.layoutInfo.assertVisibleItems(count = 3, startIndex = 0)
+
+        rule.waitForIdle()
+        rule.onNodeWithTag(TEST_TAG).performTouchInput {
+            // Swipe by an amount that is not a whole item + gap
+            swipeWithVelocity(
+                start = Offset(centerX, bottom),
+                end = Offset(centerX, bottom - (itemSizePx.toFloat())),
+                endVelocity = 1f, // Ensure it's not a fling.
+            )
+        }
+
+        rule.waitForIdle()
+        state.layoutInfo.assertVisibleItems(count = 4, startIndex = 0)
+        assertThat(state.centerItemIndex).isEqualTo(1)
+        assertThat(state.centerItemScrollOffset).isEqualTo(snapOffsetPx)
+    }
+
+    @Test
+    fun visibleItemsAreCorrectAfterScrollingReverseLayout() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            WithTouchSlop(0f) {
+                ScalingLazyColumn(
+                    state = rememberScalingLazyListState().also { state = it },
+                    modifier = Modifier.testTag(TEST_TAG).requiredSize(
+                        itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                    ),
+                    reverseLayout = true,
+                    autoCentering = null
+                ) {
+                    items(5) {
+                        Box(Modifier.requiredSize(itemSizeDp))
+                    }
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.onNodeWithTag(TEST_TAG).performTouchInput {
+            swipeDown(
+                startY = top,
+                endY = top + (itemSizePx.toFloat() + defaultItemSpacingPx.toFloat())
+            )
+        }
+        rule.waitForIdle()
+        state.layoutInfo.assertVisibleItems(count = 4, startIndex = 1)
+    }
+
+    @Test
+    fun visibleItemsAreCorrectAfterScrollNoScaling() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            WithTouchSlop(0f) {
+                ScalingLazyColumn(
+                    state = rememberScalingLazyListState(initialCenterItemIndex = 0)
+                        .also { state = it },
+                    modifier = Modifier.testTag(TEST_TAG).requiredSize(
+                        itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                    ),
+                    scalingParams = ScalingLazyColumnDefaults.scalingParams(1.0f, 1.0f),
+                    autoCentering = AutoCenteringParams(itemIndex = 0)
+                ) {
+                    items(5) {
+                        Box(Modifier.requiredSize(itemSizeDp).testTag("Item:" + it))
+                    }
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.waitForIdle()
+        rule.mainClock.autoAdvance = false
+        rule.onNodeWithTag(TEST_TAG).performTouchInput {
+            swipeUp(
+                startY = bottom,
+                endY = bottom - (itemSizePx.toFloat() + defaultItemSpacingPx.toFloat()),
+            )
+        }
+        rule.waitForIdle()
+        state.layoutInfo.assertVisibleItems(count = 4, startIndex = 0)
+        assertThat(state.centerItemIndex).isEqualTo(1)
+    }
+
+    @Test
+    fun visibleItemsAreCorrectAfterScrollNoScalingForReverseLayout() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            WithTouchSlop(0f) {
+                ScalingLazyColumn(
+                    state = rememberScalingLazyListState(8).also { state = it },
+                    modifier = Modifier.testTag(TEST_TAG).requiredSize(
+                        itemSizeDp * 4f + defaultItemSpacingDp * 3f
+                    ),
+                    scalingParams = ScalingLazyColumnDefaults.scalingParams(1.0f, 1.0f),
+                    reverseLayout = true
+                ) {
+                    items(15) {
+                        Box(Modifier.requiredSize(itemSizeDp).testTag("Item:" + it))
+                    }
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.waitForIdle()
+        rule.mainClock.autoAdvance = false
+        rule.onNodeWithTag(TEST_TAG).performTouchInput {
+            swipeDown(
+                startY = top,
+                endY = top + (itemSizePx.toFloat() + defaultItemSpacingPx.toFloat())
+            )
+        }
+        rule.waitForIdle()
+        state.layoutInfo.assertVisibleItems(count = 5, startIndex = 7)
+        assertThat(state.centerItemIndex).isEqualTo(9)
+        assertThat(state.centerItemScrollOffset).isEqualTo(0)
+    }
+
+    @Composable
+    fun ObservingFun(
+        state: ScalingLazyListState,
+        currentInfo: StableRef<ScalingLazyListLayoutInfo?>
+    ) {
+        currentInfo.value = state.layoutInfo
+    }
+
+    @Test
+    fun visibleItemsAreObservableWhenWeScroll() {
+        lateinit var state: ScalingLazyListState
+        val currentInfo = StableRef<ScalingLazyListLayoutInfo?>(null)
+        rule.setContent {
+            WithTouchSlop(0f) {
+                ScalingLazyColumn(
+                    state = rememberScalingLazyListState().also { state = it },
+                    modifier = Modifier
+                        .testTag(TEST_TAG)
+                        .requiredSize(itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f),
+                    autoCentering = null
+                ) {
+                    items(6) {
+                        Box(Modifier.requiredSize(itemSizeDp))
+                    }
+                }
+                ObservingFun(state, currentInfo)
+            }
+        }
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.waitForIdle()
+        rule.mainClock.autoAdvance = false
+        currentInfo.value = null
+        rule.onNodeWithTag(TEST_TAG).performTouchInput {
+            swipeUp(
+                startY = bottom,
+                endY = bottom - (itemSizePx.toFloat() + defaultItemSpacingPx.toFloat()),
+            )
+        }
+        rule.waitForIdle()
+        rule.mainClock.advanceTimeBy(milliseconds = 1000)
+        assertThat(currentInfo.value).isNotNull()
+        currentInfo.value!!.assertVisibleItems(count = 4, startIndex = 1)
+    }
+
+    fun ScalingLazyListLayoutInfo.assertVisibleItems(
+        count: Int,
+        startIndex: Int = 0,
+        unscaledSize: Int = itemSizePx,
+        spacing: Int = defaultItemSpacingPx,
+        anchorType: ScalingLazyListAnchorType = ScalingLazyListAnchorType.ItemCenter
+    ) {
+        assertThat(visibleItemsInfo.size).isEqualTo(count)
+        var currentIndex = startIndex
+        var previousEndOffset = -1
+        visibleItemsInfo.forEach {
+            assertThat(it.index).isEqualTo(currentIndex)
+            assertThat(it.size).isEqualTo((unscaledSize * it.scale).roundToInt())
+            currentIndex++
+            val startOffset = it.startOffset(anchorType).roundToInt()
+            if (previousEndOffset != -1) {
+                assertThat(spacing).isEqualTo(startOffset - previousEndOffset)
+            }
+            previousEndOffset = startOffset + it.size
+        }
+    }
+
+    @Test
+    fun itemFillingParentWidth() {
+        lateinit var state: ScalingLazyListState
+        rule.setContentWithTestViewConfiguration {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(8).also { state = it },
+                modifier = Modifier.requiredSize(width = 100.dp, height = 150.dp),
+                contentPadding = PaddingValues(horizontal = 0.dp),
+                scalingParams = ScalingLazyColumnDefaults.scalingParams(1.0f, 1.0f)
+            ) {
+                items(listOf(0)) {
+                    Spacer(
+                        Modifier.fillParentMaxWidth().requiredHeight(50.dp).testTag(firstItemTag)
+                    )
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.onNodeWithTag(firstItemTag)
+            .assertWidthIsEqualTo(100.dp)
+            .assertHeightIsEqualTo(50.dp)
+    }
+
+    @Test
+    fun itemFillingParentHeight() {
+        lateinit var state: ScalingLazyListState
+        rule.setContentWithTestViewConfiguration {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(8).also { state = it },
+                modifier = Modifier.requiredSize(width = 100.dp, height = 150.dp),
+                scalingParams = ScalingLazyColumnDefaults.scalingParams(1.0f, 1.0f)
+            ) {
+                items(listOf(0)) {
+                    Spacer(
+                        Modifier.requiredWidth(50.dp).fillParentMaxHeight().testTag(firstItemTag)
+                    )
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.onNodeWithTag(firstItemTag)
+            .assertWidthIsEqualTo(50.dp)
+            .assertHeightIsEqualTo(150.dp)
+    }
+
+    @Test
+    fun itemFillingParentSize() {
+        lateinit var state: ScalingLazyListState
+        rule.setContentWithTestViewConfiguration {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(8).also { state = it },
+                modifier = Modifier.requiredSize(width = 100.dp, height = 150.dp),
+                contentPadding = PaddingValues(horizontal = 0.dp),
+                scalingParams = ScalingLazyColumnDefaults.scalingParams(1.0f, 1.0f)
+            ) {
+                items(listOf(0)) {
+                    Spacer(Modifier.fillParentMaxSize().testTag(firstItemTag))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.onNodeWithTag(firstItemTag)
+            .assertWidthIsEqualTo(100.dp)
+            .assertHeightIsEqualTo(150.dp)
+    }
+
+    @Test
+    fun itemFillingParentWidthFraction() {
+        lateinit var state: ScalingLazyListState
+        rule.setContentWithTestViewConfiguration {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(8).also { state = it },
+                modifier = Modifier.requiredSize(width = 100.dp, height = 150.dp),
+                contentPadding = PaddingValues(horizontal = 0.dp),
+                scalingParams = ScalingLazyColumnDefaults.scalingParams(1.0f, 1.0f)
+            ) {
+                items(listOf(0)) {
+                    Spacer(
+                        Modifier.fillParentMaxWidth(0.7f)
+                            .requiredHeight(50.dp)
+                            .testTag(firstItemTag)
+                    )
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.onNodeWithTag(firstItemTag)
+            .assertWidthIsEqualTo(70.dp)
+            .assertHeightIsEqualTo(50.dp)
+    }
+
+    @Test
+    fun itemFillingParentHeightFraction() {
+        lateinit var state: ScalingLazyListState
+        rule.setContentWithTestViewConfiguration {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(8).also { state = it },
+                modifier = Modifier.requiredSize(width = 100.dp, height = 150.dp)
+            ) {
+                items(listOf(0)) {
+                    Spacer(
+                        Modifier.requiredWidth(50.dp)
+                            .fillParentMaxHeight(0.3f)
+                            .testTag(firstItemTag)
+                    )
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.onNodeWithTag(firstItemTag)
+            .assertWidthIsEqualTo(50.dp)
+            .assertHeightIsEqualTo(45.dp)
+    }
+
+    @Test
+    fun itemFillingParentSizeFraction() {
+        lateinit var state: ScalingLazyListState
+        rule.setContentWithTestViewConfiguration {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(8).also { state = it },
+                modifier = Modifier.requiredSize(width = 100.dp, height = 150.dp),
+                contentPadding = PaddingValues(horizontal = 0.dp)
+            ) {
+                items(listOf(0)) {
+                    Spacer(Modifier.fillParentMaxSize(0.5f).testTag(firstItemTag))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.onNodeWithTag(firstItemTag)
+            .assertWidthIsEqualTo(50.dp)
+            .assertHeightIsEqualTo(75.dp)
+    }
+
+    @Test
+    fun itemFillingParentSizeParentResized() {
+        lateinit var state: ScalingLazyListState
+        var parentSize by mutableStateOf(100.dp)
+        rule.setContentWithTestViewConfiguration {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(8).also { state = it },
+                modifier = Modifier.requiredSize(parentSize),
+                contentPadding = PaddingValues(horizontal = 0.dp),
+            ) {
+                items(listOf(0)) {
+                    Spacer(Modifier.fillParentMaxSize().testTag(firstItemTag))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            parentSize = 150.dp
+        }
+
+        rule.onNodeWithTag(firstItemTag)
+            .assertWidthIsEqualTo(150.dp)
+            .assertHeightIsEqualTo(150.dp)
+    }
+
+    @Test
+    fun listSizeFitsContentsIfNotSet() {
+        lateinit var state: ScalingLazyListState
+        var itemSize by mutableStateOf(100.dp)
+        rule.setContentWithTestViewConfiguration {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(8).also { state = it },
+                modifier = Modifier.testTag(scalingLazyColumnTag),
+                contentPadding = PaddingValues(horizontal = 0.dp),
+            ) {
+                items(listOf(0)) {
+                    Spacer(Modifier.size(itemSize).testTag(firstItemTag))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+
+        rule.onNodeWithTag(scalingLazyColumnTag)
+            .assertWidthIsEqualTo(itemSize)
+
+        rule.runOnIdle {
+            itemSize = 150.dp
+        }
+
+        rule.onNodeWithTag(scalingLazyColumnTag)
+            .assertWidthIsEqualTo(itemSize)
+
+        rule.runOnIdle {
+            itemSize = 50.dp
+        }
+
+        rule.onNodeWithTag(scalingLazyColumnTag)
+            .assertWidthIsEqualTo(itemSize)
+    }
+
+    @Test
+    fun listStateUsesInitialCenterItemIndex() {
+        val startIndexValue = 5
+        lateinit var state: ScalingLazyListState
+
+        rule.setContent {
+            state = rememberScalingLazyListState(initialCenterItemIndex = startIndexValue)
+        }
+
+        assertThat(state.centerItemIndex).isEqualTo(startIndexValue)
+    }
+
+    @Test
+    fun listStateUsesInitialCenterItemScrollOffset() {
+        val startScrollValue = 5
+        lateinit var state: ScalingLazyListState
+
+        rule.setContent {
+            state = rememberScalingLazyListState(initialCenterItemScrollOffset = startScrollValue)
+        }
+
+        assertThat(state.centerItemScrollOffset).isEqualTo(startScrollValue)
+    }
+
+    @Test
+    fun scrollToNotVisibleListWorks() {
+        lateinit var state: ScalingLazyListState
+        lateinit var showList: MutableState<Boolean>
+        rule.setContent {
+            showList = remember { mutableStateOf(true) }
+            state = rememberScalingLazyListState()
+            if (showList.value) {
+                ScalingLazyColumn(
+                    state = state,
+                    modifier = Modifier.testTag(TEST_TAG).requiredSize(
+                        itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                    ),
+                    autoCentering = AutoCenteringParams()
+                ) {
+                    items(25) {
+                        Box(Modifier.requiredSize(itemSizeDp))
+                    }
+                }
+            } else {
+                Box(modifier = Modifier.requiredSize(
+                    itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                ))
+            }
+        }
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+        rule.onNodeWithTag(TEST_TAG).assertIsDisplayed()
+
+        showList.value = false
+
+        rule.waitForIdle()
+        rule.onNodeWithTag(TEST_TAG).assertDoesNotExist()
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollToItem(10)
+            }
+        }
+
+        rule.waitForIdle()
+        showList.value = true
+
+        rule.waitForIdle()
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+
+        state.layoutInfo.assertVisibleItems(count = 5, startIndex = 8)
+        assertThat(state.centerItemIndex).isEqualTo(10)
+        assertThat(state.centerItemScrollOffset).isEqualTo(0)
+    }
+
+    @Test
+    fun scrollToNonExistentItemWorks() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            state = rememberScalingLazyListState()
+            ScalingLazyColumn(
+                state = state,
+                modifier = Modifier.testTag(TEST_TAG).requiredSize(
+                    itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                ),
+                autoCentering = AutoCenteringParams()
+            ) {
+                items(25) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollToItem(50)
+            }
+        }
+
+        state.layoutInfo.assertVisibleItems(count = 3, startIndex = 22)
+        assertThat(state.centerItemIndex).isEqualTo(24)
+        assertThat(state.centerItemScrollOffset).isEqualTo(0)
+    }
+}
+
+internal const val TestTouchSlop = 18f
+
+internal fun ComposeContentTestRule.setContentWithTestViewConfiguration(
+    composable: @Composable () -> Unit
+) {
+    this.setContent {
+        WithTouchSlop(TestTouchSlop, composable)
+    }
+}
diff --git a/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyListLayoutInfoTest.kt b/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyListLayoutInfoTest.kt
new file mode 100644
index 0000000..913c3ce7
--- /dev/null
+++ b/wear/compose/compose-foundation/src/androidAndroidTest/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyListLayoutInfoTest.kt
@@ -0,0 +1,1247 @@
+/*
+ * 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.foundation.lazy
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.animateScrollBy
+import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.math.roundToInt
+import org.junit.Ignore
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+public class ScalingLazyListLayoutInfoTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    private var itemSizePx: Int = 50
+    private var itemSizeDp: Dp = Dp.Infinity
+    private var defaultItemSpacingDp: Dp = 4.dp
+    private var defaultItemSpacingPx = Int.MAX_VALUE
+
+    @Before
+    fun before() {
+        with(rule.density) {
+            itemSizeDp = itemSizePx.toDp()
+            defaultItemSpacingPx = defaultItemSpacingDp.roundToPx()
+        }
+    }
+
+    @Ignore("Awaiting fix for b/236217874")
+    @Test
+    fun visibleItemsAreCorrect() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState().also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                ),
+                autoCentering = AutoCenteringParams()
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            assertThat(state.centerItemIndex).isEqualTo(1)
+            assertThat(state.centerItemScrollOffset).isEqualTo(0)
+            state.layoutInfo.assertVisibleItems(count = 4)
+        }
+    }
+
+    @Test
+    fun centerItemIndexIsCorrectAfterScrolling() {
+        lateinit var state: ScalingLazyListState
+        var itemSpacingPx: Int = -1
+        val itemSpacingDp = 20.dp
+        var scope: CoroutineScope? = null
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            itemSpacingPx = with(LocalDensity.current) { itemSpacingDp.roundToPx() }
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState().also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 3.5f + itemSpacingDp * 2.5f
+                ),
+                verticalArrangement = Arrangement.spacedBy(itemSpacingDp),
+                autoCentering = AutoCenteringParams()
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            assertThat(state.centerItemIndex).isEqualTo(1)
+            assertThat(state.centerItemScrollOffset).isEqualTo(0)
+            state.layoutInfo.assertVisibleItems(count = 3, spacing = itemSpacingPx)
+        }
+
+        // Scroll so that the center item is just above the center line and check that it is still
+        // the correct center item
+        val scrollDistance = (itemSizePx / 2) + 1
+        scope!!.launch {
+            state.animateScrollBy(scrollDistance.toFloat())
+        }
+        rule.runOnIdle {
+            assertThat(state.centerItemIndex).isEqualTo(1)
+            assertThat(state.centerItemScrollOffset).isEqualTo(scrollDistance)
+        }
+    }
+
+    @Test
+    fun orientationIsCorrect() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState().also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                ),
+                autoCentering = AutoCenteringParams(),
+                contentPadding = PaddingValues(all = 0.dp)
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+
+        rule.runOnIdle {
+            assertThat(state.layoutInfo.orientation).isEqualTo(Orientation.Vertical)
+        }
+    }
+
+    @Test
+    fun reverseLayoutIsCorrectWhenNotReversed() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState().also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                ),
+                autoCentering = AutoCenteringParams(),
+                contentPadding = PaddingValues(all = 0.dp)
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+
+        rule.runOnIdle {
+            assertThat(state.layoutInfo.reverseLayout).isEqualTo(false)
+        }
+    }
+
+    @Test
+    fun reverseLayoutIsCorrectWhenReversed() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState().also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                ),
+                autoCentering = AutoCenteringParams(),
+                contentPadding = PaddingValues(all = 0.dp),
+                reverseLayout = true
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+
+        rule.runOnIdle {
+            assertThat(state.layoutInfo.reverseLayout).isEqualTo(true)
+        }
+    }
+
+    @Test
+    fun visibleItemsAreCorrectSetExplicitInitialItemIndex() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(initialCenterItemIndex = 0)
+                    .also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                ),
+                autoCentering = AutoCenteringParams(itemIndex = 0)
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            assertThat(state.centerItemIndex).isEqualTo(0)
+            assertThat(state.centerItemScrollOffset).isEqualTo(0)
+            state.layoutInfo.assertVisibleItems(count = 3)
+        }
+    }
+
+    @Test
+    fun visibleItemsAreCorrectNoAutoCentering() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState().also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                ),
+                autoCentering = null
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            state.layoutInfo.assertVisibleItems(count = 4)
+        }
+    }
+
+    @Test
+    fun visibleItemsAreCorrectForReverseLayout() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState().also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                ),
+                reverseLayout = true,
+                autoCentering = null
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            assertThat(state.centerItemIndex).isEqualTo(1)
+            state.layoutInfo.assertVisibleItems(count = 4)
+        }
+    }
+
+    @Test
+    fun visibleItemsAreCorrectForReverseLayoutWithAutoCentering() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(initialCenterItemIndex = 0)
+                    .also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                ),
+                reverseLayout = true,
+                autoCentering = AutoCenteringParams(itemIndex = 0)
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            assertThat(state.centerItemIndex).isEqualTo(0)
+            assertThat(state.centerItemScrollOffset).isEqualTo(0)
+            state.layoutInfo.assertVisibleItems(count = 3)
+        }
+    }
+
+    @Test
+    fun visibleItemsAreCorrectAfterScrolling() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState().also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                ),
+                autoCentering = null
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollBy(itemSizePx.toFloat() + defaultItemSpacingPx.toFloat())
+            }
+            state.layoutInfo.assertVisibleItems(count = 4, startIndex = 1)
+        }
+    }
+
+    @Test
+    fun itemsCorrectScrollPastStartEndAutoCenterItemZeroOddHeightViewportOddHeightItems() {
+        visibleItemsAreCorrectAfterScrollingPastEndOfItems(0, 41, false)
+    }
+
+    @Test
+    fun itemsCorrectScrollPastStartEndAutoCenterItemZeroOddHeightViewportEvenHeightItems() {
+        visibleItemsAreCorrectAfterScrollingPastEndOfItems(0, 40, false)
+    }
+
+    @Test
+    fun itemsCorrectScrollPastStartEndAutoCenterItemZeroEvenHeightViewportOddHeightItems() {
+        visibleItemsAreCorrectAfterScrollingPastEndOfItems(0, 41, true)
+    }
+
+    @Test
+    fun itemsCorrectScrollPastStartEndAutoCenterItemZeroEvenHeightViewportEvenHeightItems() {
+        visibleItemsAreCorrectAfterScrollingPastEndOfItems(0, 40, true)
+    }
+
+    @Test
+    fun itemsCorrectScrollPastStartEndAutoCenterItemOneOddHeightViewportOddHeightItems() {
+        visibleItemsAreCorrectAfterScrollingPastEndOfItems(1, 41, false)
+    }
+
+    @Test
+    fun itemsCorrectScrollPastStartEndAutoCenterItemOneOddHeightViewportEvenHeightItems() {
+        visibleItemsAreCorrectAfterScrollingPastEndOfItems(1, 40, false)
+    }
+
+    @Test
+    fun itemsCorrectScrollPastStartEndAutoCenterItemOneEvenHeightViewportOddHeightItems() {
+        visibleItemsAreCorrectAfterScrollingPastEndOfItems(1, 41, true)
+    }
+
+    @Test
+    fun itemsCorrectScrollPastStartEndAutoCenterItemOneEvenHeightViewportEvenHeightItems() {
+        visibleItemsAreCorrectAfterScrollingPastEndOfItems(1, 40, true)
+    }
+
+    private fun visibleItemsAreCorrectAfterScrollingPastEndOfItems(
+        autoCenterItem: Int,
+        localItemSizePx: Int,
+        viewPortSizeEven: Boolean
+    ) {
+        lateinit var state: ScalingLazyListState
+        lateinit var scope: CoroutineScope
+        rule.setContent {
+            with(LocalDensity.current) {
+                val viewportSizePx =
+                    (((localItemSizePx * 4 + defaultItemSpacingPx * 3) / 2) * 2) +
+                        if (viewPortSizeEven) 0 else 1
+                scope = rememberCoroutineScope()
+                ScalingLazyColumn(
+                    state = rememberScalingLazyListState(
+                        initialCenterItemIndex = autoCenterItem
+                    ).also { state = it },
+                    modifier = Modifier.requiredSize(
+                        viewportSizePx.toDp()
+                    ),
+                    autoCentering = AutoCenteringParams(itemIndex = autoCenterItem)
+                ) {
+                    items(5) {
+                        Box(Modifier.requiredSize(localItemSizePx.toDp()))
+                    }
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        scope.launch {
+            state.animateScrollBy(localItemSizePx.toFloat() * 10)
+        }
+
+        rule.waitUntil { !state.isScrollInProgress }
+        assertThat(state.centerItemIndex).isEqualTo(4)
+        assertThat(state.centerItemScrollOffset).isEqualTo(0)
+
+        scope.launch {
+            state.animateScrollBy(- localItemSizePx.toFloat() * 10)
+        }
+
+        rule.waitUntil { !state.isScrollInProgress }
+        assertThat(state.centerItemIndex).isEqualTo(autoCenterItem)
+        assertThat(state.centerItemScrollOffset).isEqualTo(0)
+    }
+
+    @Test
+    fun largeItemLargerThanViewPortDoesNotGetScaled() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState().also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp
+                ),
+                autoCentering = null
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp * 5))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollBy(itemSizePx.toFloat() + defaultItemSpacingPx.toFloat())
+            }
+            val firstItem = state.layoutInfo.visibleItemsInfo.first()
+            assertThat(firstItem.offset).isLessThan(0)
+            assertThat(firstItem.offset + firstItem.size).isGreaterThan(itemSizePx)
+            assertThat(state.layoutInfo.visibleItemsInfo.first().scale).isEqualTo(1.0f)
+        }
+    }
+
+    @Test
+    fun itemInsideScalingLinesDoesNotGetScaled() {
+        lateinit var state: ScalingLazyListState
+        val centerItemIndex = 2
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(centerItemIndex).also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 3
+                ),
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            // Get the middle item on the screen
+            val centerScreenItem =
+                state.layoutInfo.visibleItemsInfo.find { it.index == centerItemIndex }
+            // and confirm its offset is 0
+            assertThat(centerScreenItem!!.offset).isEqualTo(0)
+            // And that it is not scaled
+            assertThat(centerScreenItem.scale).isEqualTo(1.0f)
+        }
+    }
+
+    @Test
+    fun itemOutsideScalingLinesDoesGetScaled() {
+        lateinit var state: ScalingLazyListState
+        val centerItemIndex = 2
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(centerItemIndex).also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 4 + defaultItemSpacingDp * 3
+                ),
+            ) {
+                items(6) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            // Get the middle item on the screen
+            val edgeScreenItem =
+                state.layoutInfo.visibleItemsInfo.find { it.index == 0 }
+
+            // And that it is it scaled
+            assertThat(edgeScreenItem!!.scale).isLessThan(1.0f)
+        }
+    }
+
+    @Test
+    fun visibleItemsAreCorrectAfterScrollingReverseLayout() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState().also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                ),
+                reverseLayout = true,
+                autoCentering = null
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollBy(itemSizePx.toFloat() + defaultItemSpacingPx.toFloat())
+            }
+            state.layoutInfo.assertVisibleItems(count = 4, startIndex = 1)
+        }
+    }
+
+    @Test
+    fun visibleItemsAreCorrectCenterPivotNoOffset() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(2).also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 2f + defaultItemSpacingDp * 1f
+                ),
+                scalingParams = ScalingLazyColumnDefaults.scalingParams(1.0f, 1.0f)
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            state.layoutInfo.assertVisibleItems(count = 3, startIndex = 1)
+            assertThat(state.centerItemIndex).isEqualTo(2)
+            assertThat(state.centerItemScrollOffset).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun visibleItemsAreCorrectCenterPivotWithOffset() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(2, -5).also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 2f + defaultItemSpacingDp * 1f
+                ),
+                scalingParams = ScalingLazyColumnDefaults.scalingParams(1.0f, 1.0f)
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            state.layoutInfo.assertVisibleItems(count = 3, startIndex = 1)
+            assertThat(state.centerItemIndex).isEqualTo(2)
+            assertThat(state.centerItemScrollOffset).isEqualTo(-5)
+        }
+    }
+
+    @Test
+    fun visibleItemsAreCorrectCenterPivotNoOffsetReverseLayout() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(2).also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 2f + defaultItemSpacingDp * 1f
+                ),
+                scalingParams = ScalingLazyColumnDefaults.scalingParams(1.0f, 1.0f),
+                reverseLayout = true
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            state.layoutInfo.assertVisibleItems(count = 3, startIndex = 1)
+            assertThat(state.centerItemIndex).isEqualTo(2)
+            assertThat(state.centerItemScrollOffset).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun visibleItemsAreCorrectCenterPivotWithOffsetReverseLayout() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(2, -5).also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 2f + defaultItemSpacingDp * 1f
+                ),
+                scalingParams = ScalingLazyColumnDefaults.scalingParams(1.0f, 1.0f),
+                reverseLayout = true
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            state.layoutInfo.assertVisibleItems(count = 3, startIndex = 1)
+            assertThat(state.centerItemIndex).isEqualTo(2)
+            assertThat(state.centerItemScrollOffset).isEqualTo(-5)
+        }
+    }
+
+    @Test
+    fun visibleItemsAreCorrectNoScalingForReverseLayout() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(8).also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 4f + defaultItemSpacingDp * 3f
+                ),
+                scalingParams = ScalingLazyColumnDefaults.scalingParams(1.0f, 1.0f),
+                reverseLayout = true
+            ) {
+                items(15) {
+                    Box(Modifier.requiredSize(itemSizeDp).testTag("Item:$it"))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.waitForIdle()
+
+        // Assert that items are being shown at the end of the parent as this is reverseLayout
+        rule.onNodeWithTag(testTag = "Item:8").assertIsDisplayed()
+
+        rule.runOnIdle {
+            state.layoutInfo.assertVisibleItems(count = 5, startIndex = 6)
+        }
+    }
+
+    @Test
+    fun visibleItemsAreCorrectAfterScrollNoScaling() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(initialCenterItemIndex = 0)
+                    .also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                ),
+                scalingParams = ScalingLazyColumnDefaults.scalingParams(1.0f, 1.0f),
+                autoCentering = AutoCenteringParams(itemIndex = 0)
+            ) {
+                items(5) {
+                    Box(
+                        Modifier
+                            .requiredSize(itemSizeDp)
+                            .testTag("Item:$it"))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.waitForIdle()
+
+        rule.onNodeWithTag(testTag = "Item:0").assertIsDisplayed()
+
+        val scrollAmount = (itemSizePx.toFloat() + defaultItemSpacingPx.toFloat()).roundToInt()
+        rule.runOnIdle {
+            assertThat(state.centerItemIndex).isEqualTo(0)
+            assertThat(state.centerItemScrollOffset).isEqualTo(0)
+
+            runBlocking {
+                state.scrollBy(scrollAmount.toFloat())
+            }
+            state.layoutInfo.assertVisibleItems(count = 4)
+            assertThat(state.layoutInfo.visibleItemsInfo.first().offset).isEqualTo(-scrollAmount)
+        }
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollBy(-scrollAmount.toFloat())
+            }
+            state.layoutInfo.assertVisibleItems(count = 3)
+            assertThat(state.layoutInfo.visibleItemsInfo.first().offset).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun visibleItemsAreCorrectAfterScrollNoScalingForReverseLayout() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(8).also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 4f + defaultItemSpacingDp * 3f
+                ),
+                scalingParams = ScalingLazyColumnDefaults.scalingParams(1.0f, 1.0f),
+                reverseLayout = true
+            ) {
+                items(15) {
+                    Box(Modifier.requiredSize(itemSizeDp).testTag("Item:$it"))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.waitForIdle()
+
+        rule.onNodeWithTag(testTag = "Item:8").assertIsDisplayed()
+
+        val scrollAmount = (itemSizePx.toFloat() + defaultItemSpacingPx.toFloat()).roundToInt()
+        rule.runOnIdle {
+            state.layoutInfo.assertVisibleItems(count = 5, startIndex = 6)
+            assertThat(state.centerItemIndex).isEqualTo(8)
+            assertThat(state.centerItemScrollOffset).isEqualTo(0)
+
+            runBlocking {
+                state.scrollBy(scrollAmount.toFloat())
+            }
+            state.layoutInfo.assertVisibleItems(count = 5, startIndex = 7)
+        }
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollBy(-scrollAmount.toFloat())
+            }
+            state.layoutInfo.assertVisibleItems(count = 5, startIndex = 6)
+        }
+    }
+
+    @Test
+    fun visibleItemsAreCorrectAfterDispatchRawDeltaScrollNoScaling() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState(initialCenterItemIndex = 0)
+                    .also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                ),
+                scalingParams = ScalingLazyColumnDefaults.scalingParams(1.0f, 1.0f),
+                autoCentering = AutoCenteringParams(itemIndex = 0)
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        val scrollAmount = itemSizePx.toFloat() + defaultItemSpacingPx.toFloat()
+        rule.runOnIdle {
+            runBlocking {
+                state.dispatchRawDelta(scrollAmount)
+            }
+            state.layoutInfo.assertVisibleItems(count = 4, startIndex = 0)
+            assertThat(state.layoutInfo.visibleItemsInfo.first().offset)
+                .isEqualTo(-scrollAmount.roundToInt())
+        }
+
+        rule.runOnIdle {
+            runBlocking {
+                state.dispatchRawDelta(-scrollAmount)
+            }
+            state.layoutInfo.assertVisibleItems(count = 3, startIndex = 0)
+            assertThat(state.layoutInfo.visibleItemsInfo.first().offset).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun visibleItemsAreCorrectAfterDispatchRawDeltaScrollNoScalingForReverseLayout() {
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState().also { state = it },
+                modifier = Modifier.requiredSize(
+                    itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f
+                ),
+                scalingParams = ScalingLazyColumnDefaults.scalingParams(1.0f, 1.0f),
+                reverseLayout = true,
+                autoCentering = null
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        val firstItemOffset = state.layoutInfo.visibleItemsInfo.first().offset
+        rule.runOnIdle {
+            runBlocking {
+                state.dispatchRawDelta(itemSizePx.toFloat() + defaultItemSpacingPx.toFloat())
+            }
+            state.layoutInfo.assertVisibleItems(count = 4, startIndex = 1)
+            assertThat(state.layoutInfo.visibleItemsInfo.first().offset).isEqualTo(firstItemOffset)
+        }
+
+        rule.runOnIdle {
+            runBlocking {
+                state.dispatchRawDelta(-(itemSizePx.toFloat() + defaultItemSpacingPx.toFloat()))
+            }
+            state.layoutInfo.assertVisibleItems(count = 4, startIndex = 0)
+            assertThat(state.layoutInfo.visibleItemsInfo.first().offset).isEqualTo(firstItemOffset)
+        }
+    }
+
+    @Test
+    fun visibleItemsAreCorrectWithCustomSpacing() {
+        lateinit var state: ScalingLazyListState
+        val spacing: Dp = 10.dp
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState().also { state = it },
+                modifier = Modifier.requiredSize(itemSizeDp * 3.5f + spacing * 2.5f),
+                verticalArrangement = Arrangement.spacedBy(spacing),
+                autoCentering = null
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            val spacingPx = with(rule.density) {
+                spacing.roundToPx()
+            }
+            state.layoutInfo.assertVisibleItems(
+                count = 4,
+                spacing = spacingPx
+            )
+        }
+    }
+
+    @Composable
+    fun ObservingFun(
+        state: ScalingLazyListState,
+        currentInfo: StableRef<ScalingLazyListLayoutInfo?>
+    ) {
+        currentInfo.value = state.layoutInfo
+    }
+
+    @Test
+    fun visibleItemsAreObservableWhenWeScroll() {
+        lateinit var state: ScalingLazyListState
+        val currentInfo = StableRef<ScalingLazyListLayoutInfo?>(null)
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState().also { state = it },
+                modifier = Modifier.requiredSize(itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f),
+                autoCentering = null
+            ) {
+                items(6) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+            ObservingFun(state, currentInfo)
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            // empty it here and scrolling should invoke observingFun again
+            currentInfo.value = null
+            runBlocking {
+                state.scrollBy(itemSizePx.toFloat() + defaultItemSpacingPx.toFloat())
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(currentInfo.value).isNotNull()
+            currentInfo.value!!.assertVisibleItems(count = 4, startIndex = 1)
+        }
+    }
+
+    @Test
+    fun visibleItemsAreObservableWhenWeDispatchRawDeltaScroll() {
+        lateinit var state: ScalingLazyListState
+        val currentInfo = StableRef<ScalingLazyListLayoutInfo?>(null)
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState().also { state = it },
+                modifier = Modifier.requiredSize(itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f),
+                autoCentering = null
+            ) {
+                items(6) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+            ObservingFun(state, currentInfo)
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            // empty it here and scrolling should invoke observingFun again
+            currentInfo.value = null
+            runBlocking {
+                state.dispatchRawDelta(itemSizePx.toFloat() + defaultItemSpacingPx.toFloat())
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(currentInfo.value).isNotNull()
+            currentInfo.value!!.assertVisibleItems(count = 4, startIndex = 1)
+        }
+    }
+
+    @Composable
+    fun ObservingIsScrollInProgressTrueFun(
+        state: ScalingLazyListState,
+        currentInfo: StableRef<Boolean?>
+    ) {
+        // If isScrollInProgress is ever true record it - otherwise leave the value as null
+        if (state.isScrollInProgress) {
+            currentInfo.value = true
+        }
+    }
+
+    @Test
+    fun isScrollInProgressIsObservableWhenWeScroll() {
+        lateinit var state: ScalingLazyListState
+        lateinit var scope: CoroutineScope
+        val currentInfo = StableRef<Boolean?>(null)
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState().also { state = it },
+                modifier = Modifier.requiredSize(itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f)
+            ) {
+                items(6) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+            ObservingIsScrollInProgressTrueFun(state, currentInfo)
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        scope.launch {
+            // empty it here and scrolling should invoke observingFun again
+            currentInfo.value = null
+            state.animateScrollBy(itemSizePx.toFloat() + defaultItemSpacingPx.toFloat())
+        }
+
+        rule.runOnIdle {
+            assertThat(currentInfo.value).isNotNull()
+            assertThat(currentInfo.value).isTrue()
+        }
+    }
+
+    @Composable
+    fun ObservingCentralItemIndexFun(
+        state: ScalingLazyListState,
+        currentInfo: StableRef<Int?>
+    ) {
+        currentInfo.value = state.centerItemIndex
+    }
+
+    @Test
+    fun isCentralListItemIndexObservableWhenWeScroll() {
+        lateinit var state: ScalingLazyListState
+        lateinit var scope: CoroutineScope
+        val currentInfo = StableRef<Int?>(null)
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState().also { state = it },
+                modifier = Modifier.requiredSize(itemSizeDp * 3.5f + defaultItemSpacingDp * 2.5f),
+                autoCentering = null
+            ) {
+                items(6) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+            ObservingCentralItemIndexFun(state, currentInfo)
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        scope.launch {
+            // empty it here and scrolling should invoke observingFun again
+            currentInfo.value = null
+            state.animateScrollBy(itemSizePx.toFloat() + defaultItemSpacingPx.toFloat())
+        }
+
+        rule.runOnIdle {
+            assertThat(currentInfo.value).isNotNull()
+            assertThat(currentInfo.value).isEqualTo(2)
+        }
+    }
+
+    @Test
+    fun visibleItemsAreObservableWhenResize() {
+        lateinit var state: ScalingLazyListState
+        var size by mutableStateOf(itemSizeDp * 2)
+        var currentInfo: ScalingLazyListLayoutInfo? = null
+        @Composable
+        fun observingFun() {
+            currentInfo = state.layoutInfo
+        }
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState().also { state = it }
+            ) {
+                item {
+                    Box(Modifier.requiredSize(size))
+                }
+            }
+            observingFun()
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            assertThat(currentInfo).isNotNull()
+            currentInfo!!.assertVisibleItems(count = 1, unscaledSize = itemSizePx * 2)
+            currentInfo = null
+            size = itemSizeDp
+        }
+
+        rule.runOnIdle {
+            assertThat(currentInfo).isNotNull()
+            currentInfo!!.assertVisibleItems(count = 1, unscaledSize = itemSizePx)
+        }
+    }
+
+    @Test
+    fun viewportOffsetsAndSizeAreCorrect() {
+        val sizePx = 45
+        val sizeDp = with(rule.density) { sizePx.toDp() }
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                Modifier.requiredSize(sizeDp),
+                state = rememberScalingLazyListState().also { state = it }
+            ) {
+                items(4) {
+                    Box(Modifier.requiredSize(sizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            assertThat(state.layoutInfo.viewportStartOffset).isEqualTo(0)
+            assertThat(state.layoutInfo.viewportEndOffset).isEqualTo(sizePx)
+            assertThat(state.layoutInfo.viewportSize).isEqualTo(IntSize(sizePx, sizePx))
+            assertThat(state.layoutInfo.beforeContentPadding).isEqualTo(0)
+            assertThat(state.layoutInfo.afterContentPadding).isEqualTo(0)
+            assertThat(state.layoutInfo.beforeAutoCenteringPadding).isEqualTo(0)
+            assertThat(state.layoutInfo.afterAutoCenteringPadding).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun viewportOffsetsAndSizeAreCorrectWithContentPadding() {
+        val sizePx = 45
+        val startPaddingPx = 10
+        val endPaddingPx = 15
+        val sizeDp = with(rule.density) { sizePx.toDp() }
+        val topPaddingDp = with(rule.density) { startPaddingPx.toDp() }
+        val bottomPaddingDp = with(rule.density) { endPaddingPx.toDp() }
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                Modifier.requiredSize(sizeDp),
+                contentPadding = PaddingValues(top = topPaddingDp, bottom = bottomPaddingDp),
+                state = rememberScalingLazyListState().also { state = it }
+            ) {
+                items(4) {
+                    Box(Modifier.requiredSize(sizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            assertThat(state.layoutInfo.viewportStartOffset).isEqualTo(-startPaddingPx)
+            assertThat(state.layoutInfo.viewportEndOffset).isEqualTo(sizePx - startPaddingPx)
+            assertThat(state.layoutInfo.viewportSize).isEqualTo(IntSize(sizePx, sizePx))
+            assertThat(state.layoutInfo.beforeContentPadding).isEqualTo(10)
+            assertThat(state.layoutInfo.afterContentPadding).isEqualTo(15)
+            assertThat(state.layoutInfo.beforeAutoCenteringPadding).isEqualTo(0)
+            assertThat(state.layoutInfo.afterAutoCenteringPadding).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun viewportOffsetsAreCorrectWithAutoCentering() {
+        val itemSizePx = 45
+        val itemSizeDp = with(rule.density) { itemSizePx.toDp() }
+        val viewPortSizePx = itemSizePx * 4
+        val viewPortSizeDp = with(rule.density) { viewPortSizePx.toDp() }
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                Modifier.requiredSize(viewPortSizeDp),
+                state = rememberScalingLazyListState(
+                    initialCenterItemIndex = 0
+                ).also { state = it },
+                autoCentering = AutoCenteringParams()
+            ) {
+                items(7) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            assertThat(state.layoutInfo.viewportStartOffset).isEqualTo(0)
+            assertThat(state.layoutInfo.viewportEndOffset).isEqualTo(viewPortSizePx)
+            assertThat(state.layoutInfo.beforeContentPadding).isEqualTo(0)
+            assertThat(state.layoutInfo.afterContentPadding).isEqualTo(0)
+            assertThat(state.layoutInfo.beforeAutoCenteringPadding).isGreaterThan(0)
+            assertThat(state.layoutInfo.afterAutoCenteringPadding).isEqualTo(0)
+
+            runBlocking {
+                state.scrollToItem(3)
+            }
+            assertThat(state.layoutInfo.beforeAutoCenteringPadding).isEqualTo(0)
+            assertThat(state.layoutInfo.afterAutoCenteringPadding).isEqualTo(0)
+
+            runBlocking {
+                state.scrollToItem(5)
+            }
+            assertThat(state.layoutInfo.beforeAutoCenteringPadding).isEqualTo(0)
+            assertThat(state.layoutInfo.afterAutoCenteringPadding).isGreaterThan(0)
+        }
+    }
+
+    @Test
+    fun totalCountIsCorrect() {
+        var count by mutableStateOf(10)
+        lateinit var state: ScalingLazyListState
+        rule.setContent {
+            ScalingLazyColumn(
+                state = rememberScalingLazyListState().also { state = it }
+            ) {
+                items(count) {
+                    Box(Modifier.requiredSize(10.dp))
+                }
+            }
+        }
+
+        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
+        rule.waitUntil { state.initialized.value }
+        rule.runOnIdle {
+            assertThat(state.layoutInfo.totalItemsCount).isEqualTo(10)
+            count = 20
+        }
+
+        rule.runOnIdle {
+            assertThat(state.layoutInfo.totalItemsCount).isEqualTo(20)
+        }
+    }
+
+    private fun ScalingLazyListLayoutInfo.assertVisibleItems(
+        count: Int,
+        startIndex: Int = 0,
+        unscaledSize: Int = itemSizePx,
+        spacing: Int = defaultItemSpacingPx,
+        anchorType: ScalingLazyListAnchorType = ScalingLazyListAnchorType.ItemCenter
+    ) {
+        assertThat(visibleItemsInfo.size).isEqualTo(count)
+        var currentIndex = startIndex
+        var previousEndOffset = -1
+        visibleItemsInfo.forEach {
+            assertThat(it.index).isEqualTo(currentIndex)
+            assertThat(it.size).isEqualTo((unscaledSize * it.scale).roundToInt())
+            currentIndex++
+            val startOffset = it.startOffset(anchorType).roundToInt()
+            if (previousEndOffset != -1) {
+                assertThat(spacing).isEqualTo(startOffset - previousEndOffset)
+            }
+            previousEndOffset = startOffset + it.size
+        }
+    }
+}
+
+@Stable
+public class StableRef<T>(var value: T)
\ No newline at end of file
diff --git a/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyColumn.kt b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyColumn.kt
new file mode 100644
index 0000000..a03e3d5
--- /dev/null
+++ b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyColumn.kt
@@ -0,0 +1,739 @@
+/*
+ * 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.foundation.lazy
+
+import androidx.annotation.RestrictTo
+import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.animation.core.DecayAnimationSpec
+import androidx.compose.animation.core.Easing
+import androidx.compose.animation.core.exponentialDecay
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ScrollableDefaults
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.calculateEndPadding
+import androidx.compose.foundation.layout.calculateStartPadding
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clipToBounds
+import androidx.compose.ui.graphics.TransformOrigin
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalInspectionMode
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.offset
+
+/**
+ * Receiver scope which is used by [ScalingLazyColumn].
+ */
+@ScalingLazyScopeMarker
+public sealed interface ScalingLazyListScope {
+    /**
+     * Adds a single item.
+     *
+     * @param key a stable and unique key representing the item. Using the same key
+     * for multiple items in the list is not allowed. Type of the key should be saveable
+     * via Bundle on Android. If null is passed the position in the list will represent the key.
+     * When you specify the key the scroll position will be maintained based on the key, which
+     * means if you add/remove items before the current visible item the item with the given key
+     * will be kept as the first visible one.
+     * @param content the content of the item
+     */
+    fun item(key: Any? = null, content: @Composable ScalingLazyListItemScope.() -> Unit)
+
+    /**
+     * Adds a [count] of items.
+     *
+     * @param count the items count
+     * @param key a factory of stable and unique keys representing the item. Using the same key
+     * for multiple items in the list is not allowed. Type of the key should be saveable
+     * via Bundle on Android. If null is passed the position in the list will represent the key.
+     * When you specify the key the scroll position will be maintained based on the key, which
+     * means if you add/remove items before the current visible item the item with the given key
+     * will be kept as the first visible one.
+     * @param itemContent the content displayed by a single item
+     */
+    fun items(
+        count: Int,
+        key: ((index: Int) -> Any)? = null,
+        itemContent: @Composable ScalingLazyListItemScope.(index: Int) -> Unit
+    )
+}
+
+/**
+ * Adds a list of items.
+ *
+ * @param items the data list
+ * @param key a factory of stable and unique keys representing the item. Using the same key
+ * for multiple items in the list is not allowed. Type of the key should be saveable
+ * via Bundle on Android. If null is passed the position in the list will represent the key.
+ * When you specify the key the scroll position will be maintained based on the key, which
+ * means if you add/remove items before the current visible item the item with the given key
+ * will be kept as the first visible one.
+ * @param itemContent the content displayed by a single item
+ */
+inline fun <T> ScalingLazyListScope.items(
+    items: List<T>,
+    noinline key: ((item: T) -> Any)? = null,
+    crossinline itemContent: @Composable ScalingLazyListItemScope.(item: T) -> Unit
+) = items(items.size, if (key != null) { index: Int -> key(items[index]) } else null) {
+    itemContent(items[it])
+}
+
+/**
+ * Adds a list of items where the content of an item is aware of its index.
+ *
+ * @param items the data list
+ * @param key a factory of stable and unique keys representing the item. Using the same key
+ * for multiple items in the list is not allowed. Type of the key should be saveable
+ * via Bundle on Android. If null is passed the position in the list will represent the key.
+ * When you specify the key the scroll position will be maintained based on the key, which
+ * means if you add/remove items before the current visible item the item with the given key
+ * will be kept as the first visible one.
+ * @param itemContent the content displayed by a single item
+ */
+inline fun <T> ScalingLazyListScope.itemsIndexed(
+    items: List<T>,
+    noinline key: ((index: Int, item: T) -> Any)? = null,
+    crossinline itemContent: @Composable ScalingLazyListItemScope.(index: Int, item: T) -> Unit
+) = items(items.size, if (key != null) { index: Int -> key(index, items[index]) } else null) {
+    itemContent(it, items[it])
+}
+
+/**
+ * Adds an array of items.
+ *
+ * @param items the data array
+ * @param key a factory of stable and unique keys representing the item. Using the same key
+ * for multiple items in the list is not allowed. Type of the key should be saveable
+ * via Bundle on Android. If null is passed the position in the list will represent the key.
+ * When you specify the key the scroll position will be maintained based on the key, which
+ * means if you add/remove items before the current visible item the item with the given key
+ * will be kept as the first visible one.
+ * @param itemContent the content displayed by a single item
+ */
+inline fun <T> ScalingLazyListScope.items(
+    items: Array<T>,
+    noinline key: ((item: T) -> Any)? = null,
+    crossinline itemContent: @Composable ScalingLazyListItemScope.(item: T) -> Unit
+) = items(items.size, if (key != null) { index: Int -> key(items[index]) } else null) {
+    itemContent(items[it])
+}
+
+/**
+ * Adds an array of items where the content of an item is aware of its index.
+ *
+ * @param items the data array
+ * @param key a factory of stable and unique keys representing the item. Using the same key
+ * for multiple items in the list is not allowed. Type of the key should be saveable
+ * via Bundle on Android. If null is passed the position in the list will represent the key.
+ * When you specify the key the scroll position will be maintained based on the key, which
+ * means if you add/remove items before the current visible item the item with the given key
+ * will be kept as the first visible one.
+ * @param itemContent the content displayed by a single item
+ */
+public inline fun <T> ScalingLazyListScope.itemsIndexed(
+    items: Array<T>,
+    noinline key: ((index: Int, item: T) -> Any)? = null,
+    crossinline itemContent: @Composable ScalingLazyListItemScope.(index: Int, item: T) -> Unit
+) = items(items.size, if (key != null) { index: Int -> key(index, items[index]) } else null) {
+    itemContent(it, items[it])
+}
+
+@Immutable
+@kotlin.jvm.JvmInline
+public value class ScalingLazyListAnchorType internal constructor(internal val type: Int) {
+
+    companion object {
+        /**
+         * Place the center of the item on (or as close to) the center line of the viewport
+         */
+        val ItemCenter = ScalingLazyListAnchorType(0)
+
+        /**
+         * Place the start (edge) of the item on, or as close to as possible, the center line of the
+         * viewport. For normal layout this will be the top edge of the item, for reverseLayout it
+         * will be the bottom edge.
+         */
+        val ItemStart = ScalingLazyListAnchorType(1)
+    }
+
+    override fun toString(): String {
+        return when (this) {
+            ItemStart -> "ScalingLazyListAnchorType.ItemStart"
+            else -> "ScalingLazyListAnchorType.ItemCenter"
+        }
+    }
+}
+
+/**
+ * Parameters to determine which list item and offset to calculate auto-centering spacing for. The
+ * default values are [itemIndex] = 1 and [itemOffset] = 0. This will provide sufficient padding for
+ * the second item (index = 1) in the list being centerable. This is to match the Wear UX
+ * guidelines that a typical list will have a ListHeader item as the first item in the list
+ * (index = 0) and that this should not be scrollable into the middle of the viewport, instead the
+ * first list item that a user can interact with (index = 1) would be the first that would be in the
+ * center.
+ *
+ * If your use case is different and you want all list items to be able to be scrolled to the
+ * viewport middle, including the first item in the list then set [itemIndex] = 0.
+ *
+ * The higher the value for [itemIndex] you provide the less auto centering padding will be
+ * provided as the amount of padding needed to allow that item to be centered will reduce.
+ * Even for a list of short items setting [itemIndex] above 3 or 4 is likely
+ * to result in no auto-centering padding being provided as items with index 3 or 4 will probably
+ * already be naturally scrollable to the center of the viewport.
+ *
+ * [itemOffset] allows adjustment of the items position relative the [ScalingLazyColumn]s
+ * [ScalingLazyListAnchorType]. This can be useful if you need fine grained control over item
+ * positioning and spacing, e.g. If you are lining up the gaps between two items on the viewport
+ * center line where you would want to set the offset to half the distance between listItems in
+ * pixels.
+ *
+ * See also [rememberScalingLazyListState] where similar fields are provided to allow control over
+ * the initially selected centered item index and offset. By default these match the auto centering
+ * defaults meaning that the second item (index = 1) will be the item scrolled to the viewport
+ * center.
+ *
+ * @param itemIndex Which list item index to enable auto-centering from. Space (padding) will be
+ * added such that items with index [itemIndex] or greater will be able to be scrolled to the center
+ * of the viewport. If the developer wants to add additional space to allow other list items to also
+ * be scrollable to the center they can use contentPadding on the ScalingLazyColumn. If the
+ * developer wants custom control over position and spacing they can switch off autoCentering
+ * and provide contentPadding.
+ *
+ * @param itemOffset What offset, if any, to apply when calculating space for auto-centering
+ * the [itemIndex] item. E.g. itemOffset can be used if the developer wants to align the viewport
+ * center in the gap between two list items.
+ *
+ * For an example of a [ScalingLazyColumn] with an explicit itemOffset see:
+ * @sample androidx.wear.compose.foundation.samples.ScalingLazyColumnEdgeAnchoredAndAnimatedScrollTo
+ */
+@Immutable
+public class AutoCenteringParams(
+    // @IntRange(from = 0)
+    internal val itemIndex: Int = 1,
+    internal val itemOffset: Int = 0,
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        return (other is AutoCenteringParams) &&
+            itemIndex == other.itemIndex &&
+            itemOffset == other.itemOffset
+    }
+
+    override fun hashCode(): Int {
+        var result = itemIndex
+        result = 31 * result + itemOffset
+        return result
+    }
+}
+
+/**
+ * A scrolling scaling/fisheye list component that forms a key part of the Wear Material Design
+ * language. Provides scaling and transparency effects to the content items.
+ *
+ * [ScalingLazyColumn] is designed to be able to handle potentially large numbers of content
+ * items. Content items are only materialized and composed when needed.
+ *
+ * If scaling/fisheye functionality is not required then a [LazyColumn] should be considered
+ * instead to avoid any overhead of measuring and calculating scaling and transparency effects for
+ * the content items.
+ *
+ * Example of a [ScalingLazyColumn] with default parameters:
+ * @sample androidx.wear.compose.foundation.samples.SimpleScalingLazyColumn
+ *
+ * Example of a [ScalingLazyColumn] using [ScalingLazyListAnchorType.ItemStart] anchoring, in this
+ * configuration the edge of list items is aligned to the center of the screen. Also this example
+ * shows scrolling to a clicked list item with [ScalingLazyListState.animateScrollToItem]:
+ * @sample androidx.wear.compose.foundation.samples.ScalingLazyColumnEdgeAnchoredAndAnimatedScrollTo
+ *
+ * Example of a [ScalingLazyColumn] with snap of items to the viewport center:
+ * @sample androidx.wear.compose.foundation.samples.SimpleScalingLazyColumnWithSnap
+ *
+ * Example of a [ScalingLazyColumn] where [autoCentering] has been disabled and explicit
+ * [contentPadding] provided to ensure there is space above the first and below the last list item
+ * to allow them to be scrolled into view on circular screens:
+ * @sample androidx.wear.compose.foundation.samples.SimpleScalingLazyColumnWithContentPadding
+ *
+ * For more information, see the
+ * [Lists](https://developer.android.com/training/wearables/components/lists)
+ * guide.
+ *
+ * @param modifier The modifier to be applied to the component
+ * @param state The state of the component
+ * @param contentPadding The padding to apply around the contents
+ * @param reverseLayout reverse the direction of scrolling and layout, when `true` items will be
+ * composed from the bottom to the top
+ * @param verticalArrangement The vertical arrangement of the layout's children. This allows us
+ * to add spacing between items and specify the arrangement of the items when we have not enough
+ * of them to fill the whole minimum size
+ * @param horizontalAlignment the horizontal alignment applied to the items
+ * @param flingBehavior Logic describing fling behavior. If snapping is required use
+ * [ScalingLazyColumnDefaults.snapFlingBehavior].
+ * @param userScrollEnabled whether the scrolling via the user gestures or accessibility actions
+ * is allowed. You can still scroll programmatically using the state even when it is disabled.
+ * @param scalingParams The parameters to configure the scaling and transparency effects for the
+ * component
+ * @param anchorType How to anchor list items to the center-line of the viewport
+ * @param autoCentering AutoCenteringParams parameter to control whether space/padding should be
+ * automatically added to make sure that list items can be scrolled into the center of the viewport
+ * (based on their [anchorType]). If non-null then space will be added before the first list item,
+ * if needed, to ensure that items with indexes greater than or equal to the itemIndex (offset by
+ * itemOffset pixels) will be able to be scrolled to the center of the viewport. Similarly space
+ * will be added at the end of the list to ensure that items can be scrolled up to the center. If
+ * null no automatic space will be added and instead the developer can use [contentPadding] to
+ * manually arrange the items.
+ * @param content The content of the [ScalingLazyColumn]
+ */
+@Composable
+public fun ScalingLazyColumn(
+    modifier: Modifier = Modifier,
+    state: ScalingLazyListState = rememberScalingLazyListState(),
+    contentPadding: PaddingValues = PaddingValues(horizontal = 10.dp),
+    reverseLayout: Boolean = false,
+    verticalArrangement: Arrangement.Vertical =
+        Arrangement.spacedBy(
+            space = 4.dp,
+            alignment = if (!reverseLayout) Alignment.Top else Alignment.Bottom
+        ),
+    horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
+    flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
+    userScrollEnabled: Boolean = true,
+    scalingParams: ScalingParams = ScalingLazyColumnDefaults.scalingParams(),
+    anchorType: ScalingLazyListAnchorType = ScalingLazyListAnchorType.ItemCenter,
+    autoCentering: AutoCenteringParams? = AutoCenteringParams(),
+    content: ScalingLazyListScope.() -> Unit
+) {
+    var initialized by remember { mutableStateOf(false) }
+    BoxWithConstraints(modifier = modifier, propagateMinConstraints = true) {
+        val density = LocalDensity.current
+        val layoutDirection = LocalLayoutDirection.current
+        val extraPaddingInPixels = scalingParams.resolveViewportVerticalOffset(constraints)
+
+        with(density) {
+            val extraPadding = extraPaddingInPixels.toDp()
+            val combinedPaddingValues = CombinedPaddingValues(
+                contentPadding = contentPadding,
+                extraPadding = extraPadding
+            )
+
+            val beforeContentPaddingInPx =
+                if (reverseLayout) contentPadding.calculateBottomPadding().roundToPx()
+                else contentPadding.calculateTopPadding().roundToPx()
+
+            val afterContentPaddingInPx =
+                if (reverseLayout) contentPadding.calculateTopPadding().roundToPx()
+                else contentPadding.calculateBottomPadding().roundToPx()
+
+            val itemScope =
+                ScalingLazyListItemScopeImpl(
+                    density = density,
+                    constraints = constraints.offset(
+                        horizontal = -(
+                            contentPadding.calculateStartPadding(layoutDirection) +
+                                contentPadding.calculateEndPadding(layoutDirection)
+                            ).toPx().toInt(),
+                        vertical = -(
+                            contentPadding.calculateTopPadding() +
+                                contentPadding.calculateBottomPadding()
+                            ).roundToPx()
+                    )
+                )
+
+            // Set up transient state
+            state.scalingParams.value = scalingParams
+            state.extraPaddingPx.value = extraPaddingInPixels
+            state.beforeContentPaddingPx.value = beforeContentPaddingInPx
+            state.afterContentPaddingPx.value = afterContentPaddingInPx
+            state.viewportHeightPx.value = constraints.maxHeight
+            state.gapBetweenItemsPx.value =
+                verticalArrangement.spacing.roundToPx()
+            state.anchorType.value = anchorType
+            state.autoCentering.value = autoCentering
+            state.reverseLayout.value = reverseLayout
+            state.localInspectionMode.value = LocalInspectionMode.current
+
+            LazyColumn(
+                modifier = Modifier
+                    .clipToBounds()
+                    .verticalNegativePadding(extraPadding)
+                    .onGloballyPositioned {
+                        val layoutInfo = state.layoutInfo
+                        if (!initialized &&
+                            layoutInfo is DefaultScalingLazyListLayoutInfo &&
+                            layoutInfo.readyForInitialScroll
+                        ) {
+                            initialized = true
+                        }
+                    },
+                horizontalAlignment = horizontalAlignment,
+                contentPadding = combinedPaddingValues,
+                reverseLayout = reverseLayout,
+                verticalArrangement = verticalArrangement,
+                state = state.lazyListState,
+                flingBehavior = flingBehavior,
+                userScrollEnabled = userScrollEnabled,
+            ) {
+                val scope = ScalingLazyListScopeImpl(
+                    state = state,
+                    scope = this,
+                    itemScope = itemScope
+                )
+                // Only add spacers if autoCentering == true as we have to consider the impact of
+                // vertical spacing between items.
+                if (autoCentering != null) {
+                    item {
+                        Spacer(
+                            modifier = Modifier.height(state.topAutoCenteringItemSizePx.toDp())
+                        )
+                    }
+                }
+                scope.content()
+                if (autoCentering != null) {
+                    item {
+                        Spacer(
+                            modifier = Modifier.height(state.bottomAutoCenteringItemSizePx.toDp())
+                        )
+                    }
+                }
+            }
+            if (initialized) {
+                LaunchedEffect(state) {
+                    state.scrollToInitialItem()
+                }
+            }
+        }
+    }
+}
+
+/**
+ * Contains the default values used by [ScalingLazyColumn]
+ */
+public object ScalingLazyColumnDefaults {
+    /**
+     * Creates a [ScalingParams] that represents the scaling and alpha properties for a
+     * [ScalingLazyColumn].
+     *
+     * Items in the ScalingLazyColumn have scaling and alpha effects applied to them depending on
+     * their position in the viewport. The closer to the edge (top or bottom) of the viewport that
+     * they are the greater the down scaling and transparency that is applied. Note that scaling and
+     * transparency effects are applied from the center of the viewport (full size and normal
+     * transparency) towards the edge (items can be smaller and more transparent).
+     *
+     * Deciding how much scaling and alpha to apply is based on the position and size of the item
+     * and on a series of properties that are used to determine the transition area for each item.
+     *
+     * The transition area is defined by the edge of the screen and a transition line which is
+     * calculated for each item in the list. The items transition line is based upon its size with
+     * the potential for larger list items to start their transition earlier (closer to the center)
+     * than smaller items.
+     *
+     * [minTransitionArea] and [maxTransitionArea] are both in the range [0f..1f] and are
+     * the fraction of the distance between the edge of the viewport and the center of
+     * the viewport. E.g. a value of 0.2f for minTransitionArea and 0.75f for maxTransitionArea
+     * determines that all transition lines will fall between 1/5th (20%) and 3/4s (75%) of the
+     * distance between the viewport edge and center.
+     *
+     * The size of the each item is used to determine where within the transition area range
+     * minTransitionArea..maxTransitionArea the actual transition line will be. [minElementHeight]
+     * and [maxElementHeight] are used along with the item height (as a fraction of the viewport
+     * height in the range [0f..1f]) to find the transition line. So if the items size is 0.25f
+     * (25%) of way between minElementSize..maxElementSize then the transition line will be 0.25f
+     * (25%) of the way between minTransitionArea..maxTransitionArea.
+     *
+     * A list item smaller than minElementHeight is rounded up to minElementHeight and larger than
+     * maxElementHeight is rounded down to maxElementHeight. Whereabouts the items height sits
+     * between minElementHeight..maxElementHeight is then used to determine where the transition
+     * line sits between minTransitionArea..maxTransition area.
+     *
+     * If an item is smaller than or equal to minElementSize its transition line with be at
+     * minTransitionArea and if it is larger than or equal to maxElementSize its transition line
+     * will be  at maxTransitionArea.
+     *
+     * For example, if we take the default values for minTransitionArea = 0.2f and
+     * maxTransitionArea = 0.6f and minElementSize = 0.2f and maxElementSize= 0.8f then an item
+     * with a height of 0.4f (40%) of the viewport height is one third of way between
+     * minElementSize and maxElementSize, (0.4f - 0.2f) / (0.8f - 0.2f) = 0.33f. So its transition
+     * line would be one third of way between 0.2f and 0.6f, transition line = 0.2f + (0.6f -
+     * 0.2f) * 0.33f = 0.33f.
+     *
+     * Once the position of the transition line is established we now have a transition area
+     * for the item, e.g. in the example above the item will start/finish its transitions when it
+     * is 0.33f (33%) of the distance from the edge of the viewport and will start/finish its
+     * transitions at the viewport edge.
+     *
+     * The scaleInterpolator is used to determine how much of the scaling and alpha to apply
+     * as the item transits through the transition area.
+     *
+     * The edge of the item furthest from the edge of the screen is used as a scaling trigger
+     * point for each item.
+     *
+     * @param edgeScale What fraction of the full size of the item to scale it by when most
+     * scaled, e.g. at the edge of the viewport. A value between [0f,1f], so a value of 0.2f
+     * means to scale an item to 20% of its normal size.
+     *
+     * @param edgeAlpha What fraction of the full transparency of the item to draw it with
+     * when closest to the edge of the screen. A value between [0f,1f], so a value of
+     * 0.2f means to set the alpha of an item to 20% of its normal value.
+     *
+     * @param minElementHeight The minimum element height as a ratio of the viewport size to use
+     * for determining the transition point within ([minTransitionArea], [maxTransitionArea])
+     * that a given content item will start to be transitioned. Items smaller than
+     * [minElementHeight] will be treated as if [minElementHeight]. Must be less than or equal to
+     * [maxElementHeight].
+     *
+     * @param maxElementHeight The maximum element height as a ratio of the viewport size to use
+     * for determining the transition point within ([minTransitionArea], [maxTransitionArea])
+     * that a given content item will start to be transitioned. Items larger than [maxElementHeight]
+     * will be treated as if [maxElementHeight]. Must be greater than or equal to
+     * [minElementHeight].
+     *
+     * @param minTransitionArea The lower bound of the transition line area, closest to the
+     * edge of the viewport. Defined as a fraction (value between 0f..1f) of the distance between
+     * the viewport edge and viewport center line. Must be less than or equal to
+     * [maxTransitionArea].
+     *
+     * @param maxTransitionArea The upper bound of the transition line area, closest to the
+     * center of the viewport. The fraction (value between 0f..1f) of the distance
+     * between the viewport edge and viewport center line. Must be greater
+     * than or equal to [minTransitionArea].
+     *
+     * @param scaleInterpolator An interpolator to use to determine how to apply scaling as a
+     * item transitions across the scaling transition area.
+     *
+     * @param viewportVerticalOffsetResolver The additional padding to consider above and below the
+     * viewport of a [ScalingLazyColumn] when considering which items to draw in the viewport. If
+     * set to 0 then no additional padding will be provided and only the items which would appear
+     * in the viewport before any scaling is applied will be considered for drawing, this may
+     * leave blank space at the top and bottom of the viewport where the next available item
+     * could have been drawn once other items have been scaled down in size. The larger this
+     * value is set to will allow for more content items to be considered for drawing in the
+     * viewport, however there is a performance cost associated with materializing items that are
+     * subsequently not drawn. The higher/more extreme the scaling parameters that are applied to
+     * the [ScalingLazyColumn] the more padding may be needed to ensure there are always enough
+     * content items available to be rendered. By default will be 5% of the maxHeight of the
+     * viewport above and below the content.
+     */
+    fun scalingParams(
+        edgeScale: Float = 0.7f,
+        edgeAlpha: Float = 0.5f,
+        minElementHeight: Float = 0.2f,
+        maxElementHeight: Float = 0.6f,
+        minTransitionArea: Float = 0.35f,
+        maxTransitionArea: Float = 0.55f,
+        scaleInterpolator: Easing = CubicBezierEasing(0.3f, 0f, 0.7f, 1f),
+        viewportVerticalOffsetResolver: (Constraints) -> Int = { (it.maxHeight / 20f).toInt() }
+    ): ScalingParams = DefaultScalingParams(
+        edgeScale = edgeScale,
+        edgeAlpha = edgeAlpha,
+        minElementHeight = minElementHeight,
+        maxElementHeight = maxElementHeight,
+        minTransitionArea = minTransitionArea,
+        maxTransitionArea = maxTransitionArea,
+        scaleInterpolator = scaleInterpolator,
+        viewportVerticalOffsetResolver = viewportVerticalOffsetResolver
+    )
+
+    /**
+     * Create and remember a [FlingBehavior] that will represent natural fling curve with snap to
+     * central item as the fling decays.
+     *
+     * @param state the state of the [ScalingLazyColumn]
+     * @param snapOffset an optional offset to be applied when snapping the item. After the snap the
+     * snapped items offset will be [snapOffset].
+     * @param decay the decay to use
+     */
+    @Composable
+    public fun snapFlingBehavior(
+        state: ScalingLazyListState,
+        snapOffset: Dp = 0.dp,
+        decay: DecayAnimationSpec<Float> = exponentialDecay()
+    ): FlingBehavior {
+        val snapOffsetPx = with(LocalDensity.current) { snapOffset.roundToPx() }
+        return remember(state, snapOffset, decay) {
+            ScalingLazyColumnSnapFlingBehavior(
+                state = state,
+                snapOffset = snapOffsetPx,
+                decay = decay
+            )
+        }
+    }
+}
+
+private class ScalingLazyListScopeImpl(
+    private val state: ScalingLazyListState,
+    private val scope: LazyListScope,
+    private val itemScope: ScalingLazyListItemScope
+) : ScalingLazyListScope {
+
+    private var currentStartIndex = 0
+
+    override fun item(key: Any?, content: @Composable (ScalingLazyListItemScope.() -> Unit)) {
+        val startIndex = currentStartIndex
+        scope.item(key = key) {
+            ScalingLazyColumnItemWrapper(
+                startIndex,
+                state,
+                itemScope,
+                content
+            )
+        }
+        currentStartIndex++
+    }
+
+    override fun items(
+        count: Int,
+        key: ((index: Int) -> Any)?,
+        itemContent: @Composable (ScalingLazyListItemScope.(index: Int) -> Unit)
+    ) {
+        val startIndex = currentStartIndex
+        scope.items(count = count, key = key) {
+            ScalingLazyColumnItemWrapper(
+                startIndex + it,
+                state = state,
+                itemScope = itemScope
+            ) {
+                itemContent(it)
+            }
+        }
+        currentStartIndex += count
+    }
+}
+
+@Composable
+private fun ScalingLazyColumnItemWrapper(
+    index: Int,
+    state: ScalingLazyListState,
+    itemScope: ScalingLazyListItemScope,
+    content: @Composable (ScalingLazyListItemScope.() -> Unit)
+) {
+    Box(
+        modifier = Modifier.graphicsLayer {
+            val reverseLayout = state.reverseLayout.value!!
+            val anchorType = state.anchorType.value!!
+            val items = state.layoutInfo.internalVisibleItemInfo()
+            val currentItem = items.find { it.index == index }
+            if (currentItem != null) {
+                alpha = currentItem.alpha
+                scaleX = currentItem.scale
+                scaleY = currentItem.scale
+                // Calculate how much to adjust/translate the position of the list item by
+                // determining the different between the unadjusted start position based on the
+                // underlying LazyList layout and the start position adjusted to take into account
+                // scaling of the list items. Items further from the middle of the visible viewport
+                // will be subject to more adjustment.
+                if (currentItem.scale > 0f) {
+                    val offsetAdjust = currentItem.startOffset(anchorType) -
+                        currentItem.unadjustedStartOffset(anchorType)
+                    translationY = if (reverseLayout) -offsetAdjust else offsetAdjust
+                    transformOrigin = TransformOrigin(
+                        pivotFractionX = 0.5f,
+                        pivotFractionY = if (reverseLayout) 1.0f else 0.0f
+                    )
+                }
+            }
+        }
+    ) {
+        itemScope.content()
+    }
+}
+
+/** @hide **/
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@Immutable
+public class CombinedPaddingValues(
+    @Stable
+    val contentPadding: PaddingValues,
+    @Stable
+    val extraPadding: Dp
+) : PaddingValues {
+    override fun calculateLeftPadding(layoutDirection: LayoutDirection): Dp =
+        contentPadding.calculateLeftPadding(layoutDirection)
+
+    override fun calculateTopPadding(): Dp =
+        contentPadding.calculateTopPadding() + extraPadding
+
+    override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp =
+        contentPadding.calculateRightPadding(layoutDirection)
+
+    override fun calculateBottomPadding(): Dp =
+        contentPadding.calculateBottomPadding() + extraPadding
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null) return false
+        if (this::class != other::class) return false
+
+        other as CombinedPaddingValues
+
+        if (contentPadding != other.contentPadding) return false
+        if (extraPadding != other.extraPadding) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = contentPadding.hashCode()
+        result = 31 * result + extraPadding.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "CombinedPaddingValuesImpl(contentPadding=$contentPadding, " +
+            "extraPadding=$extraPadding)"
+    }
+}
+
+/** @hide **/
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public fun Modifier.verticalNegativePadding(
+    extraPadding: Dp,
+) = layout { measurable, constraints ->
+    require(constraints.hasBoundedWidth)
+    require(constraints.hasBoundedHeight)
+    val topAndBottomPadding = (extraPadding * 2).roundToPx()
+    val placeable = measurable.measure(
+        constraints.copy(
+            minHeight = constraints.minHeight + topAndBottomPadding,
+            maxHeight = constraints.maxHeight + topAndBottomPadding
+        )
+    )
+
+    layout(placeable.measuredWidth, constraints.maxHeight) {
+        placeable.place(0, -extraPadding.roundToPx())
+    }
+}
diff --git a/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyColumnMeasure.kt b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyColumnMeasure.kt
new file mode 100644
index 0000000..40e7a6a
--- /dev/null
+++ b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyColumnMeasure.kt
@@ -0,0 +1,517 @@
+/*
+ * 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.foundation.lazy
+
+import androidx.annotation.RestrictTo
+import androidx.compose.animation.core.Easing
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.lazy.LazyListItemInfo
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.util.lerp
+import kotlin.math.min
+import kotlin.math.roundToInt
+
+/**
+ * Parameters to control the scaling of the contents of a [ScalingLazyColumn].
+ *
+ * Items in the ScalingLazyColumn have scaling and alpha effects applied to them depending on
+ * their position in the viewport. The closer to the edge (top or bottom) of the viewport that
+ * they are the greater the down scaling and transparency that is applied. Note that scaling and
+ * transparency effects are applied from the center of the viewport (nearest to full size and normal
+ * transparency) towards the edge (items can be smaller and more transparent).
+ *
+ * Deciding how much scaling and alpha to apply is based on the position and size of the item
+ * and on a series of properties that are used to determine the transition area for each item.
+ *
+ * The transition area is defined by the edge of the screen and a transition line which is
+ * calculated for each item in the list. There are two symmetrical transition lines/areas one at the
+ * top of the viewport and one at the bottom.
+ *
+ * The items transition line is based upon its size with the potential for larger list items to
+ * start their transition earlier (further from the edge they are transitioning towards) than
+ * smaller items.
+ *
+ * It is possible for the transition line to be closer to the edge that the list item is moving away
+ * from, i.e. the opposite side of the center line of the viewport. This may seem counter-intuitive
+ * at first as it means that the transition lines can appear inverted. But as the two transition
+ * lines interact with the opposite edges of the list item top with bottom, bottom with top it is
+ * often desirable to have inverted transition lines for large list items.
+ *
+ * [minTransitionArea] and [maxTransitionArea] are both in the range [0f..1f] and are
+ * the fraction of the distance between the edges of the viewport. E.g. a value of 0.2f for
+ * minTransitionArea and 0.75f for maxTransitionArea determines that all transition lines will fall
+ * between 1/5th (20%) and 3/4s (75%) of the height of the viewport.
+ *
+ * The size of the each item is used to determine where within the transition area range
+ * minTransitionArea..maxTransitionArea the actual transition line will be. [minElementHeight]
+ * and [maxElementHeight] are used along with the item height (as a fraction of the viewport
+ * height in the range [0f..1f]) to find the transition line. So if the items size is 0.25f
+ * (25%) of way between minElementSize..maxElementSize then the transition line will be 0.25f
+ * (25%) of the way between minTransitionArea..maxTransitionArea.
+ *
+ * A list item smaller than minElementHeight is rounded up to minElementHeight and larger than
+ * maxElementHeight is rounded down to maxElementHeight. Whereabouts the items height sits
+ * between minElementHeight..maxElementHeight is then used to determine where the transition
+ * line sits between minTransitionArea..maxTransition area.
+ *
+ * If an item is smaller than or equal to minElementSize its transition line with be at
+ * minTransitionArea and if it is larger than or equal to maxElementSize its transition line
+ * will be  at maxTransitionArea.
+ *
+ * For example, if we take the default values for minTransitionArea = 0.2f and
+ * maxTransitionArea = 0.6f and minElementSize = 0.2f and maxElementSize= 0.8f then an item
+ * with a height of 0.4f (40%) of the viewport height is one third of way between
+ * minElementSize and maxElementSize, (0.4f - 0.2f) / (0.8f - 0.2f) = 0.33f. So its transition
+ * line would be one third of way between 0.2f and 0.6f, transition line = 0.2f + (0.6f -
+ * 0.2f) * 0.33f = 0.33f.
+ *
+ * Once the position of the transition line is established we now have a transition area
+ * for the item, e.g. in the example above the item will start/finish its transitions when it
+ * is 0.33f (33%) of the distance from the edge of the viewport and will start/finish its
+ * transitions at the viewport edge.
+ *
+ * The scaleInterpolator is used to determine how much of the scaling and alpha to apply
+ * as the item transits through the transition area.
+ *
+ * The edge of the item furthest from the edge of the screen is used as a scaling trigger
+ * point for each item.
+ */
+@Stable
+public interface ScalingParams {
+    /**
+     * What fraction of the full size of the item to scale it by when most
+     * scaled, e.g. at the edge of the viewport. A value between [0f,1f], so a value of 0.2f
+     * means to scale an item to 20% of its normal size.
+     */
+//    @FloatRange(
+//        fromInclusive = true, from = 0.0, toInclusive = true, to = 1.0
+//    )
+    val edgeScale: Float
+
+    /**
+     * What fraction of the full transparency of the item to draw it with
+     * when closest to the edge of the screen. A value between [0f,1f], so a value of
+     * 0.2f means to set the alpha of an item to 20% of its normal value.
+     */
+//    @FloatRange(
+//        fromInclusive = true, from = 0.0, toInclusive = true, to = 1.0
+//    )
+    val edgeAlpha: Float
+
+    /**
+     * The maximum element height as a ratio of the viewport size to use
+     * for determining the transition point within ([minTransitionArea], [maxTransitionArea])
+     * that a given content item will start to be transitioned. Items larger than [maxElementHeight]
+     * will be treated as if [maxElementHeight]. Must be greater than or equal to
+     * [minElementHeight].
+     */
+//    @FloatRange(
+//        fromInclusive = true, from = 0.0, toInclusive = true, to = 1.0
+//    )
+    val minElementHeight: Float
+
+    /**
+     * The maximum element height as a ratio of the viewport size to use
+     * for determining the transition point within ([minTransitionArea], [maxTransitionArea])
+     * that a given content item will start to be transitioned. Items larger than [maxElementHeight]
+     * will be treated as if [maxElementHeight]. Must be greater than or equal to
+     * [minElementHeight].
+     */
+//    @FloatRange(
+//        fromInclusive = true, from = 0.0, toInclusive = true, to = 1.0
+//    )
+    val maxElementHeight: Float
+
+    /**
+     * The lower bound of the transition line area, closest to the edge of the viewport. Defined as
+     * a fraction (value between 0f..1f) of the distance between the viewport edges. Must be less
+     * than or equal to [maxTransitionArea].
+     *
+     * For transition lines a value of 0f means that the transition line is at the viewport edge,
+     * e.g. no transition, a value of 0.25f means that the transition line is 25% of the screen size
+     * away from the viewport edge. A value of .5f or greater will place the transition line in the
+     * other half of the screen to the edge that the item is transitioning towards.
+     *
+     * [minTransitionArea] and [maxTransitionArea] provide an area in which transition lines for all
+     * list items exist. Depending on the size of the list item the specific point in the area is
+     * calculated.
+     */
+//    @FloatRange(
+//        fromInclusive = true, from = 0.0, toInclusive = true, to = 1.0
+//    )
+    val minTransitionArea: Float
+
+    /**
+     * The upper bound of the transition line area, closest to the
+     * center of the viewport. The fraction (value between 0f..1f) of the distance
+     * between the viewport edges. Must be greater than or equal to [minTransitionArea].
+     *
+     * For transition lines a value of 0f means that the transition line is at the viewport edge,
+     * e.g. no transition, a value of 0.25f means that the transition line is 25% of the screen size
+     * away from the viewport edge. A value of .5f or greater will place the transition line in the
+     * other half of the screen to the edge that the item is transitioning towards.
+     *
+     * [minTransitionArea] and [maxTransitionArea] provide an area in which transition lines for all
+     * list items exist. Depending on the size of the list item the specific point in the area is
+     * calculated.
+     */
+//    @FloatRange(
+//        fromInclusive = true, from = 0.0, toInclusive = true, to = 1.0
+//    )
+    val maxTransitionArea: Float
+
+    /**
+     * An interpolator to use to determine how to apply scaling as a item transitions across the
+     * scaling transition area.
+     */
+    val scaleInterpolator: Easing
+
+    /**
+     * The additional padding to consider above and below the
+     * viewport of a [ScalingLazyColumn] when considering which items to draw in the viewport. If
+     * set to 0 then no additional padding will be provided and only the items which would appear
+     * in the viewport before any scaling is applied will be considered for drawing, this may
+     * leave blank space at the top and bottom of the viewport where the next available item
+     * could have been drawn once other items have been scaled down in size. The larger this
+     * value is set to will allow for more content items to be considered for drawing in the
+     * viewport, however there is a performance cost associated with materializing items that are
+     * subsequently not drawn. The higher/more extreme the scaling parameters that are applied to
+     * the [ScalingLazyColumn] the more padding may be needed to ensure there are always enough
+     * content items available to be rendered. By default will be 20% of the maxHeight of the
+     * viewport above and below the content.
+     *
+     * @param viewportConstraints the viewports constraints
+     */
+    public fun resolveViewportVerticalOffset(viewportConstraints: Constraints): Int
+}
+
+@Stable
+internal class DefaultScalingParams(
+    override val edgeScale: Float,
+    override val edgeAlpha: Float,
+    override val minElementHeight: Float,
+    override val maxElementHeight: Float,
+    override val minTransitionArea: Float,
+    override val maxTransitionArea: Float,
+    override val scaleInterpolator: Easing,
+    val viewportVerticalOffsetResolver: (Constraints) -> Int,
+) : ScalingParams {
+
+    init {
+        check(
+            minElementHeight <= maxElementHeight
+        ) { "minElementHeight must be less than or equal to maxElementHeight" }
+        check(
+            minTransitionArea <= maxTransitionArea
+        ) { "minTransitionArea must be less than or equal to maxTransitionArea" }
+    }
+
+    override fun resolveViewportVerticalOffset(viewportConstraints: Constraints): Int {
+        return viewportVerticalOffsetResolver(viewportConstraints)
+    }
+
+    override fun toString(): String {
+        return "ScalingParams(edgeScale=$edgeScale, edgeAlpha=$edgeAlpha, " +
+            "minElementHeight=$minElementHeight, maxElementHeight=$maxElementHeight, " +
+            "minTransitionArea=$minTransitionArea, maxTransitionArea=$maxTransitionArea)"
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null) return false
+        if (this::class != other::class) return false
+
+        other as DefaultScalingParams
+
+        if (edgeScale != other.edgeScale) return false
+        if (edgeAlpha != other.edgeAlpha) return false
+        if (minElementHeight != other.minElementHeight) return false
+        if (maxElementHeight != other.maxElementHeight) return false
+        if (minTransitionArea != other.minTransitionArea) return false
+        if (maxTransitionArea != other.maxTransitionArea) return false
+        if (scaleInterpolator != other.scaleInterpolator) return false
+        if (viewportVerticalOffsetResolver != other.viewportVerticalOffsetResolver) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = edgeScale.hashCode()
+        result = 31 * result + edgeAlpha.hashCode()
+        result = 31 * result + minElementHeight.hashCode()
+        result = 31 * result + maxElementHeight.hashCode()
+        result = 31 * result + minTransitionArea.hashCode()
+        result = 31 * result + maxTransitionArea.hashCode()
+        result = 31 * result + scaleInterpolator.hashCode()
+        result = 31 * result + viewportVerticalOffsetResolver.hashCode()
+        return result
+    }
+}
+
+/**
+ * Calculate the scale and alpha to apply for an item based on the start and end position of the
+ * component's viewport in pixels and top and bottom position of the item, also in pixels.
+ *
+ * Firstly work out if the component is above or below the viewport's center-line which determines
+ * whether the item's top or bottom will be used as the trigger for scaling/alpha.
+ *
+ * Uses the scalingParams to determine where the scaling transition line is for the component.
+ *
+ * Then determines if the component is inside the scaling area, and if so what scaling/alpha effects
+ * to apply.
+ *
+ * @param viewPortStartPx The start position of the component's viewport in pixels
+ * @param viewPortEndPx The end position of the component's viewport in pixels
+ * @param itemTopPx The top of the content item in pixels.
+ * @param itemBottomPx The bottom of the content item in pixels.
+ * @param scalingParams The parameters that determine where the item's scaling transition line
+ * is, how scaling and transparency to apply.
+ */
+internal fun calculateScaleAndAlpha(
+    viewPortStartPx: Int,
+    viewPortEndPx: Int,
+    itemTopPx: Int,
+    itemBottomPx: Int,
+    scalingParams: ScalingParams,
+): ScaleAndAlpha {
+    var scaleToApply = 1.0f
+    var alphaToApply = 1.0f
+
+    val itemHeightPx = (itemBottomPx - itemTopPx).toFloat()
+    val viewPortHeightPx = (viewPortEndPx - viewPortStartPx).toFloat()
+
+    if (viewPortHeightPx > 0) {
+        val itemEdgeDistanceFromViewPortEdge =
+            min(viewPortEndPx - itemTopPx, itemBottomPx - viewPortStartPx)
+        val itemEdgeAsFractionOfViewPort = itemEdgeDistanceFromViewPortEdge / viewPortHeightPx
+        val heightAsFractionOfViewPort = itemHeightPx / viewPortHeightPx
+
+        // Work out the scaling line based on size, this is a value between 0.0..1.0
+        val sizeRatio =
+            inverseLerp(scalingParams.minElementHeight, scalingParams.maxElementHeight,
+                heightAsFractionOfViewPort)
+
+        val scalingLineAsFractionOfViewPort =
+            lerp(scalingParams.minTransitionArea, scalingParams.maxTransitionArea, sizeRatio)
+
+        if (itemEdgeAsFractionOfViewPort < scalingLineAsFractionOfViewPort) {
+            // We are scaling
+            val scalingProgressRaw = 1f - itemEdgeAsFractionOfViewPort /
+                scalingLineAsFractionOfViewPort
+            val scalingInterpolated = scalingParams.scaleInterpolator.transform(scalingProgressRaw)
+
+            scaleToApply = lerp(1.0f, scalingParams.edgeScale, scalingInterpolated)
+            alphaToApply = lerp(1.0f, scalingParams.edgeAlpha, scalingInterpolated)
+        }
+    }
+    return ScaleAndAlpha(scaleToApply, alphaToApply)
+}
+
+/**
+ * Create a [ScalingLazyListItemInfo] given an unscaled start and end position for an item.
+ *
+ * @param itemStart the x-axis position of a list item. The x-axis position takes into account
+ * any adjustment to the original position based on the scaling of other list items.
+ * @param item the original item info used to provide the pre-scaling position and size
+ * information for the item.
+ * @param verticalAdjustment the amount of vertical adjustment to apply to item positions to
+ * allow for content padding in order to determine the adjusted position of the item within the
+ * viewport in order to correctly calculate the scaling to apply.
+ * @param viewportHeightPx the height of the viewport in pixels
+ * @param viewportCenterLinePx the center line of the viewport in pixels
+ * @param scalingParams the scaling params to use for determining the scaled size of the item
+ * @param beforeContentPaddingPx the number of pixels of padding before the first item
+ * @param anchorType the type of pivot to use for the center item when calculating position and
+ * offset
+ * @param visible a flag to determine whether the list items should be visible or transparent.
+ * Items are normally visible, but can be drawn transparently when the list is not yet initialized,
+ * unless we are in preview (LocalInspectionModel) mode.
+ */
+internal fun calculateItemInfo(
+    itemStart: Int,
+    item: LazyListItemInfo,
+    verticalAdjustment: Int,
+    viewportHeightPx: Int,
+    viewportCenterLinePx: Int,
+    scalingParams: ScalingParams,
+    beforeContentPaddingPx: Int,
+    anchorType: ScalingLazyListAnchorType,
+    autoCentering: AutoCenteringParams?,
+    visible: Boolean
+): ScalingLazyListItemInfo {
+    val adjustedItemStart = itemStart - verticalAdjustment
+    val adjustedItemEnd = itemStart + item.size - verticalAdjustment
+
+    val scaleAndAlpha = calculateScaleAndAlpha(
+        viewPortStartPx = 0, viewPortEndPx = viewportHeightPx, itemTopPx = adjustedItemStart,
+        itemBottomPx = adjustedItemEnd, scalingParams = scalingParams
+    )
+
+    val isAboveLine = (adjustedItemEnd + adjustedItemStart) < viewportHeightPx
+    val scaledHeight = (item.size * scaleAndAlpha.scale).roundToInt()
+    val scaledItemTop = if (!isAboveLine) {
+        itemStart
+    } else {
+        itemStart + item.size - scaledHeight
+    }
+
+    val offset = convertToCenterOffset(
+        anchorType = anchorType,
+        itemScrollOffset = scaledItemTop,
+        viewportCenterLinePx = viewportCenterLinePx,
+        beforeContentPaddingInPx = beforeContentPaddingPx,
+        itemSizeInPx = scaledHeight
+    )
+    val unadjustedOffset = convertToCenterOffset(
+        anchorType = anchorType,
+        itemScrollOffset = item.offset,
+        viewportCenterLinePx = viewportCenterLinePx,
+        beforeContentPaddingInPx = beforeContentPaddingPx,
+        itemSizeInPx = item.size
+    )
+    return DefaultScalingLazyListItemInfo(
+        // Adjust index to take into account the Spacer before the first list item
+        index = if (autoCentering != null) item.index - 1 else item.index,
+        key = item.key,
+        unadjustedOffset = unadjustedOffset,
+        offset = offset,
+        size = scaledHeight,
+        scale = scaleAndAlpha.scale,
+        alpha = if (visible) scaleAndAlpha.alpha else 0f,
+        unadjustedSize = item.size
+    )
+}
+
+internal class DefaultScalingLazyListLayoutInfo(
+    internal val internalVisibleItemsInfo: List<ScalingLazyListItemInfo>,
+    override val viewportStartOffset: Int,
+    override val viewportEndOffset: Int,
+    override val totalItemsCount: Int,
+    val centerItemIndex: Int,
+    val centerItemScrollOffset: Int,
+    override val reverseLayout: Boolean,
+    override val orientation: Orientation,
+    override val viewportSize: IntSize,
+    override val beforeContentPadding: Int,
+    override val afterContentPadding: Int,
+    override val beforeAutoCenteringPadding: Int,
+    override val afterAutoCenteringPadding: Int,
+    // Flag to indicate that we are ready to handle scrolling as the list becomes visible. This is
+    // used to either move to the initialCenterItemIndex/Offset or complete any
+    // scrollTo/animatedScrollTo calls that were incomplete due to the component not being visible.
+    internal val readyForInitialScroll: Boolean,
+    // Flag to indicate that initialization is complete and initial scroll index and offset have
+    // been set.
+    internal val initialized: Boolean,
+    override val anchorType: ScalingLazyListAnchorType,
+) : ScalingLazyListLayoutInfo {
+    override val visibleItemsInfo: List<ScalingLazyListItemInfo>
+        // Do not report visible items until initialization is complete and the items are
+        // actually visible and correctly positioned.
+        get() = if (initialized) internalVisibleItemsInfo else emptyList()
+}
+
+internal class DefaultScalingLazyListItemInfo(
+    override val index: Int,
+    override val key: Any,
+    override val unadjustedOffset: Int,
+    override val offset: Int,
+    override val size: Int,
+    override val scale: Float,
+    override val alpha: Float,
+    override val unadjustedSize: Int
+) : ScalingLazyListItemInfo {
+    override fun toString(): String {
+        return "DefaultScalingLazyListItemInfo(index=$index, key=$key, " +
+            "unadjustedOffset=$unadjustedOffset, offset=$offset, size=$size, " +
+            "unadjustedSize=$unadjustedSize, scale=$scale, alpha=$alpha)"
+    }
+}
+
+/** @hide **/
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@Immutable
+public data class ScaleAndAlpha(
+    val scale: Float,
+    val alpha: Float
+
+) {
+    companion object {
+        internal val noScaling = ScaleAndAlpha(1.0f, 1.0f)
+    }
+}
+
+/**
+ * Calculate the offset from the viewport center line of the Start|Center of an items unadjusted
+ * or scaled size. The for items with an height that is an odd number and that have
+ * ScalingLazyListAnchorType.Center the offset will be rounded down. E.g. An item which is 19 pixels
+ * in height will have a center offset of 9 pixes.
+ *
+ * @param anchorType the anchor type of the ScalingLazyColumn
+ * @param itemScrollOffset the LazyListItemInfo offset of the item
+ * @param viewportCenterLinePx the value to use as the center line of the viewport
+ * @param beforeContentPaddingInPx any content padding that has been applied before the contents
+ * @param itemSizeInPx the size of the item
+ */
+internal fun convertToCenterOffset(
+    anchorType: ScalingLazyListAnchorType,
+    itemScrollOffset: Int,
+    viewportCenterLinePx: Int,
+    beforeContentPaddingInPx: Int,
+    itemSizeInPx: Int
+): Int {
+    return itemScrollOffset - viewportCenterLinePx + beforeContentPaddingInPx +
+        if (anchorType == ScalingLazyListAnchorType.ItemStart) {
+            0
+        } else {
+            itemSizeInPx / 2
+        }
+}
+
+/**
+ * Find the start offset of the list item w.r.t. the
+ */
+internal fun ScalingLazyListItemInfo.startOffset(anchorType: ScalingLazyListAnchorType) =
+    offset - if (anchorType == ScalingLazyListAnchorType.ItemCenter) {
+        (size / 2f)
+    } else {
+        0f
+    }
+
+/**
+ * Find the start position of the list item from its unadjusted offset w.r.t. the ScalingLazyColumn
+ * center of viewport offset = 0 coordinate model.
+ */
+internal fun ScalingLazyListItemInfo.unadjustedStartOffset(anchorType: ScalingLazyListAnchorType) =
+    unadjustedOffset - if (anchorType == ScalingLazyListAnchorType.ItemCenter) {
+        (unadjustedSize / 2f)
+    } else {
+        0f
+    }
+
+/**
+ * Inverse linearly interpolate, return what fraction (0f..1f) that [value] is between [start] and
+ * [stop]. Returns 0f if value =< start and 1f if value >= stop.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public fun inverseLerp(start: Float, stop: Float, value: Float): Float {
+    return ((value - start) / (stop - start)).coerceIn(0f, 1f)
+}
\ No newline at end of file
diff --git a/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyColumnSnapFlingBehavior.kt b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyColumnSnapFlingBehavior.kt
new file mode 100644
index 0000000..0d57a82
--- /dev/null
+++ b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyColumnSnapFlingBehavior.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.foundation.lazy
+
+import androidx.compose.animation.core.AnimationState
+import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.animation.core.DecayAnimationSpec
+import androidx.compose.animation.core.animateDecay
+import androidx.compose.animation.core.animateTo
+import androidx.compose.animation.core.calculateTargetValue
+import androidx.compose.animation.core.exponentialDecay
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ScrollScope
+import androidx.compose.ui.util.lerp
+import kotlin.math.abs
+import kotlin.math.roundToInt
+import kotlin.math.sqrt
+
+internal class ScalingLazyColumnSnapFlingBehavior(
+    val state: ScalingLazyListState,
+    val snapOffset: Int = 0,
+    val decay: DecayAnimationSpec<Float> = exponentialDecay()
+) : FlingBehavior {
+
+    override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
+        val animationState = AnimationState(
+            initialValue = 0f,
+            initialVelocity = initialVelocity,
+        )
+
+        var lastValue = 0f
+        val visibleItemsInfo = state.layoutInfo.visibleItemsInfo
+        val isAFling = abs(initialVelocity) > 1f && visibleItemsInfo.size > 1
+        val finalTarget = if (isAFling) {
+            // Target we will land on given initialVelocity & decay
+            val decayTarget = decay.calculateTargetValue(0f, initialVelocity)
+            var endOfListReached = false
+
+            animationState.animateDecay(decay) {
+                val delta = value - lastValue
+                val consumed = scrollBy(delta)
+                lastValue = value
+
+                // When we are "slow" enough, switch from decay to the final snap.
+                if (abs(velocity) < SNAP_SPEED_THRESHOLD) cancelAnimation()
+
+                // If we can't consume the scroll, also stop.
+                if (abs(delta - consumed) > 0.1f) {
+                    endOfListReached = true
+                    cancelAnimation()
+                }
+            }
+
+            if (endOfListReached) {
+                // We couldn't scroll as much as we wanted, likely we reached the end of the list,
+                // Snap to the current item and finish.
+                scrollBy((snapOffset - state.centerItemScrollOffset).toFloat())
+                return animationState.velocity
+            } else {
+                // Now that scrolling slowed down, adjust the animation to land in the item closest
+                // to the original target. Note that the target may be off-screen, in that case we
+                // will land on the last visible item in that direction.
+                (state.layoutInfo.visibleItemsInfo
+                    .map { animationState.value + it.unadjustedOffset + snapOffset }
+                    .minByOrNull { abs(it - decayTarget) } ?: decayTarget)
+            }
+        } else {
+            // Not a fling, just snap to the current item.
+            (snapOffset - state.centerItemScrollOffset).toFloat()
+        }
+
+        // We have a velocity (animationState.velocity), and a target (finalTarget),
+        // Construct a cubic bezier with the given initial velocity, and ending at 0 speed,
+        // unless that will mean that we need to accelerate and decelerate.
+        // We can also control the inertia of these speeds, i.e. how much it will accelerate/
+        // decelerate at the beginning and end.
+        val distance = finalTarget - animationState.value
+
+        // If the distance to fling is zero, nothing to do (and must avoid divide-by-zero below).
+        if (distance != 0.0f) {
+            val initialSpeed = animationState.velocity
+
+            // Inertia of the initial speed.
+            val initialInertia = 0.5f
+
+            // Compute how much time we want to spend on the final snap, depending on the speed
+            val finalSnapDuration = lerp(
+                FINAL_SNAP_DURATION_MIN, FINAL_SNAP_DURATION_MAX,
+                abs(initialSpeed) / SNAP_SPEED_THRESHOLD
+            )
+
+            // Initial control point. Has slope (velocity) adjustedSpeed and magnitude (inertia)
+            // initialInertia
+            val adjustedSpeed = initialSpeed * finalSnapDuration / distance
+            val easingX0 = initialInertia / sqrt(1f + adjustedSpeed * adjustedSpeed)
+            val easingY0 = easingX0 * adjustedSpeed
+
+            // Final control point. Has slope 0, unless that will make us accelerate then decelerate,
+            // in that case we set the slope to 1.
+            val easingX1 = 0.8f
+            val easingY1 = if (easingX0 > easingY0) 0.8f else 1f
+
+            animationState.animateTo(
+                finalTarget, tween(
+                    (finalSnapDuration * 1000).roundToInt(),
+                    easing = CubicBezierEasing(easingX0, easingY0, easingX1, easingY1)
+                )
+            ) {
+                scrollBy(value - lastValue)
+                lastValue = value
+            }
+        }
+
+        return animationState.velocity
+    }
+
+    // Speed, in pixels per second, to switch between decay and final snap.
+    private val SNAP_SPEED_THRESHOLD = 1200
+
+    // Minimum duration for the final snap after the fling, in seconds, used when the initial speed
+    // is 0
+    private val FINAL_SNAP_DURATION_MIN = .1f
+
+    // Maximum duration for the final snap after the fling, in seconds, used when the speed is
+    // SNAP_SPEED_THRESHOLD
+    private val FINAL_SNAP_DURATION_MAX = .35f
+}
\ No newline at end of file
diff --git a/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyListItemInfo.kt b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyListItemInfo.kt
new file mode 100644
index 0000000..b70ee81
--- /dev/null
+++ b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyListItemInfo.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.foundation.lazy
+
+/**
+ * Contains useful information about an individual item in a [ScalingLazyColumn].
+ *
+ * @see ScalingLazyListLayoutInfo
+ */
+public sealed interface ScalingLazyListItemInfo {
+    /**
+     * The index of the item in the list.
+     */
+    val index: Int
+
+    /**
+     * The key of the item which was passed to the item() or items() function.
+     */
+    val key: Any
+
+    /**
+     * The main axis offset of the item before adjustment for scaling of the items in the viewport.
+     *
+     * The offset is relative to the center-line of the viewport of the lazy list container and
+     * takes the [ScalingLazyListAnchorType] into account.
+     *
+     * For [ScalingLazyListAnchorType.ItemCenter] the offset is from the center of the list item to
+     * the center-line of the viewport.
+     *
+     * For [ScalingLazyListAnchorType.ItemStart] if is the offset
+     * between the start (edge) of the item and the center-line of the viewport, for normal layout
+     * this will be the top edge of the item, for reverseLayout it will be the bottom edge.
+     */
+    val unadjustedOffset: Int
+
+    /**
+     * The main axis offset of the item after adjustment for scaling of the items in the viewport.
+     *
+     * The offset is relative to the center-line of the viewport of the lazy list container and
+     * takes the [ScalingLazyListAnchorType] into account.
+     *
+     * For [ScalingLazyListAnchorType.ItemCenter] the offset is from the center of the list item to
+     * the center-line of the viewport.
+     *
+     * For [ScalingLazyListAnchorType.ItemStart] if is the offset
+     * between the start (edge) of the item and the center-line of the viewport, for normal layout
+     * this will be the top edge of the item, for reverseLayout it will be the bottom edge.
+     *
+     * A positive value indicates that the item's anchor point is below the viewport center-line, a
+     * negative value indicates that the item anchor point is above the viewport center-line.
+     */
+    val offset: Int
+
+    /**
+     * The scaled/adjusted main axis size of the item. Note that if you emit multiple layouts in the
+     * composable slot for the item then this size will be calculated as the sum of their sizes.
+     */
+    val size: Int
+
+    /**
+     * How much scaling has been applied to the item, between 0 and 1
+     */
+    val scale: Float
+
+    /**
+     * How much alpha has been applied to the item, between 0 and 1
+     */
+    val alpha: Float
+
+    /**
+     * The original (before scaling) size of the list item
+     */
+    val unadjustedSize: Int
+}
diff --git a/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyListItemScope.kt b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyListItemScope.kt
new file mode 100644
index 0000000..042e928
--- /dev/null
+++ b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyListItemScope.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.foundation.lazy
+
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+
+/**
+ * Receiver scope being used by the item content parameter of ScalingLazyColumn.
+ */
+@Stable
+@ScalingLazyScopeMarker
+public sealed interface ScalingLazyListItemScope {
+    /**
+     * Have the content fill the [Constraints.maxWidth] and [Constraints.maxHeight] of the parent
+     * measurement constraints by setting the [minimum width][Constraints.minWidth] to be equal to
+     * the [maximum width][Constraints.maxWidth] multiplied by [fraction] and the [minimum
+     * height][Constraints.minHeight] to be equal to the [maximum height][Constraints.maxHeight]
+     * multiplied by [fraction]. Note that, by default, the [fraction] is 1, so the modifier will
+     * make the content fill the whole available space. [fraction] must be between `0` and `1`.
+     *
+     * Regular [Modifier.fillMaxSize] can't work inside the scrolling layouts as the items are
+     * measured with [Constraints.Infinity] as the constraints for the main axis.
+     */
+    fun Modifier.fillParentMaxSize(
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        fraction: Float = 1f
+    ): Modifier
+
+    /**
+     * Have the content fill the [Constraints.maxWidth] of the parent measurement constraints
+     * by setting the [minimum width][Constraints.minWidth] to be equal to the
+     * [maximum width][Constraints.maxWidth] multiplied by [fraction]. Note that, by default, the
+     * [fraction] is 1, so the modifier will make the content fill the whole parent width.
+     * [fraction] must be between `0` and `1`.
+     *
+     * Regular [Modifier.fillMaxWidth] can't work inside the scrolling horizontally layouts as the
+     * items are measured with [Constraints.Infinity] as the constraints for the main axis.
+     */
+    fun Modifier.fillParentMaxWidth(
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        fraction: Float = 1f
+    ): Modifier
+
+    /**
+     * Have the content fill the [Constraints.maxHeight] of the incoming measurement constraints
+     * by setting the [minimum height][Constraints.minHeight] to be equal to the
+     * [maximum height][Constraints.maxHeight] multiplied by [fraction]. Note that, by default, the
+     * [fraction] is 1, so the modifier will make the content fill the whole parent height.
+     * [fraction] must be between `0` and `1`.
+     *
+     * Regular [Modifier.fillMaxHeight] can't work inside the scrolling vertically layouts as the
+     * items are measured with [Constraints.Infinity] as the constraints for the main axis.
+     */
+    fun Modifier.fillParentMaxHeight(
+        /*@FloatRange(from = 0.0, to = 1.0)*/
+        fraction: Float = 1f
+    ): Modifier
+}
+
+internal data class ScalingLazyListItemScopeImpl(
+    val density: Density,
+    val constraints: Constraints
+) : ScalingLazyListItemScope {
+    private val maxWidth: Dp = with(density) { constraints.maxWidth.toDp() }
+    private val maxHeight: Dp = with(density) { constraints.maxHeight.toDp() }
+
+    override fun Modifier.fillParentMaxSize(fraction: Float) = size(
+        maxWidth * fraction,
+        maxHeight * fraction
+    )
+
+    override fun Modifier.fillParentMaxWidth(fraction: Float) =
+        width(maxWidth * fraction)
+
+    override fun Modifier.fillParentMaxHeight(fraction: Float) =
+        height(maxHeight * fraction)
+}
diff --git a/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyListLayoutInfo.kt b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyListLayoutInfo.kt
new file mode 100644
index 0000000..dc2ccd6
--- /dev/null
+++ b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyListLayoutInfo.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.foundation.lazy
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.unit.IntSize
+
+/**
+ * Contains useful information about the currently displayed layout state of [ScalingLazyColumn].
+ * For example you can get the list of currently displayed item.
+ *
+ * Use [ScalingLazyListState.layoutInfo] to retrieve this
+ */
+public sealed interface ScalingLazyListLayoutInfo {
+    /**
+     * The list of [ScalingLazyListItemInfo] representing all the currently visible items.
+     */
+    val visibleItemsInfo: List<ScalingLazyListItemInfo>
+
+    /**
+     * The start offset of the layout's viewport in pixels. You can think of it as a minimum offset
+     * which would be visible. Usually it is 0, but it can be negative if non-zero
+     * [beforeContentPadding] was applied as the content displayed in the content padding area is
+     * still visible.
+     *
+     * You can use it to understand what items from [visibleItemsInfo] are fully visible.
+     */
+    val viewportStartOffset: Int
+
+    /**
+     * The end offset of the layout's viewport in pixels. You can think of it as a maximum offset
+     * which would be visible. It is the size of the scaling lazy list layout minus
+     * [beforeContentPadding].
+     *
+     * You can use it to understand what items from [visibleItemsInfo] are fully visible.
+     */
+    val viewportEndOffset: Int
+
+    /**
+     * The total count of items passed to [ScalingLazyColumn].
+     */
+    val totalItemsCount: Int
+
+    /**
+     * The size of the viewport in pixels. It is the scaling lazy list layout size including all the
+     * content paddings.
+     */
+    val viewportSize: IntSize
+
+    /**
+     * The orientation of the scaling lazy list.
+     */
+    val orientation: Orientation
+
+    /**
+     * True if the direction of scrolling and layout is reversed.
+     */
+    val reverseLayout: Boolean
+
+    /**
+     * The content padding in pixels applied before the first item in the direction of scrolling.
+     * For example it is a top content padding for ScalingLazyColumn with reverseLayout set to
+     * false.
+     */
+    val beforeContentPadding: Int
+
+    /**
+     * The content padding in pixels applied after the last item in the direction of scrolling.
+     * For example it is a bottom content padding for ScalingLazyColumn with reverseLayout set to
+     * false.
+     */
+    val afterContentPadding: Int
+
+    /**
+     * The auto centering padding in pixels applied before the first item in the direction of
+     * scrolling. For example it is a top auto centering padding for ScalingLazyColumn with
+     * reverseLayout set to false.
+     */
+    val beforeAutoCenteringPadding: Int
+
+    /**
+     * The auto centering padding in pixels applied after the last item in the direction of
+     * scrolling. For example it is a bottom auto centering padding for ScalingLazyColumn with
+     * reverseLayout set to false.
+     */
+    val afterAutoCenteringPadding: Int
+
+    /**
+     * How to anchor list items to the center-line of the viewport
+     */
+    val anchorType: ScalingLazyListAnchorType
+}
\ No newline at end of file
diff --git a/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyListState.kt b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyListState.kt
new file mode 100644
index 0000000..fd00b16
--- /dev/null
+++ b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyListState.kt
@@ -0,0 +1,710 @@
+/*
+ * 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.foundation.lazy
+
+import androidx.compose.foundation.MutatePriority
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollScope
+import androidx.compose.foundation.gestures.ScrollableState
+import androidx.compose.foundation.lazy.LazyListItemInfo
+import androidx.compose.foundation.lazy.LazyListLayoutInfo
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.listSaver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.unit.IntSize
+import kotlin.math.roundToInt
+
+/**
+ * Creates a [ScalingLazyListState] that is remembered across compositions.
+ *
+ * @param initialCenterItemIndex the initial value for [ScalingLazyListState.centerItemIndex],
+ * defaults to 1. This will place the 2nd list item (index == 1) in the center of the viewport and
+ * the first item (index == 0) before it.
+ *
+ * @param initialCenterItemScrollOffset the initial value for
+ * [ScalingLazyListState.centerItemScrollOffset] in pixels
+ */
+@Composable
+public fun rememberScalingLazyListState(
+    initialCenterItemIndex: Int = 1,
+    initialCenterItemScrollOffset: Int = 0
+): ScalingLazyListState {
+    return rememberSaveable(saver = ScalingLazyListState.Saver) {
+        ScalingLazyListState(
+            initialCenterItemIndex,
+            initialCenterItemScrollOffset
+        )
+    }
+}
+
+/**
+ * A state object that can be hoisted to control and observe scrolling.
+ *
+ * In most cases, this will be created via [rememberScalingLazyListState].
+ *
+ * @param initialCenterItemIndex the initial value for [ScalingLazyListState.centerItemIndex],
+ * defaults to 1. This will place the 2nd list item (index == 1) in the center of the viewport and
+ * the first item (index == 0) before it.
+ *
+ * If the developer wants custom control over position and spacing they can switch off autoCentering
+ * and provide contentPadding.
+ *
+ * @param initialCenterItemScrollOffset the initial value for
+ * [ScalingLazyListState.centerItemScrollOffset]
+ *
+ * Note that it is not always possible for the values provided by [initialCenterItemIndex] and
+ * [initialCenterItemScrollOffset] to be honored, e.g. If [initialCenterItemIndex] is set to a value
+ * larger than the number of items initially in the list, or to an index that can not be placed in
+ * the middle of the screen due to the contentPadding or autoCentering properties provided to the
+ * [ScalingLazyColumn]. After the [ScalingLazyColumn] is initially drawn the actual values for the
+ * [centerItemIndex] and [centerItemScrollOffset] can be read from the state.
+ */
+@Stable
+class ScalingLazyListState constructor(
+    private var initialCenterItemIndex: Int = 1,
+    private var initialCenterItemScrollOffset: Int = 0
+) : ScrollableState {
+
+    internal var lazyListState: LazyListState = LazyListState(0, 0)
+    internal val extraPaddingPx = mutableStateOf<Int?>(null)
+    internal val beforeContentPaddingPx = mutableStateOf<Int?>(null)
+    internal val afterContentPaddingPx = mutableStateOf<Int?>(null)
+    internal val scalingParams = mutableStateOf<ScalingParams?>(null)
+    internal val gapBetweenItemsPx = mutableStateOf<Int?>(null)
+    internal val viewportHeightPx = mutableStateOf<Int?>(null)
+    internal val reverseLayout = mutableStateOf<Boolean?>(null)
+    internal val anchorType = mutableStateOf<ScalingLazyListAnchorType?>(null)
+    internal val autoCentering = mutableStateOf<AutoCenteringParams?>(null)
+    internal val initialized = mutableStateOf<Boolean>(false)
+    internal val localInspectionMode = mutableStateOf<Boolean>(false)
+
+    // The following three are used together when there is a post-initialization incomplete scroll
+    // to finish next time the ScalingLazyColumn is visible
+    private val incompleteScrollItem = mutableStateOf<Int?>(null)
+    private val incompleteScrollOffset = mutableStateOf<Int?>(null)
+    private val incompleteScrollAnimated = mutableStateOf(false)
+
+    /**
+     * The index of the item positioned closest to the viewport center
+     */
+    public val centerItemIndex: Int
+        get() =
+            (layoutInfo as? DefaultScalingLazyListLayoutInfo)?.let {
+                if (it.initialized) it.centerItemIndex else null
+            } ?: initialCenterItemIndex
+
+    internal val topAutoCenteringItemSizePx: Int by derivedStateOf {
+        if (extraPaddingPx.value == null || scalingParams.value == null ||
+            gapBetweenItemsPx.value == null || viewportHeightPx.value == null ||
+            anchorType.value == null || reverseLayout.value == null ||
+            beforeContentPaddingPx.value == null || autoCentering.value == null ||
+            autoCentering.value == null
+        ) {
+            0
+        } else {
+            (layoutInfo.beforeAutoCenteringPadding - gapBetweenItemsPx.value!!).coerceAtLeast(0)
+        }
+    }
+
+    internal val bottomAutoCenteringItemSizePx: Int by derivedStateOf {
+        if (extraPaddingPx.value == null || scalingParams.value == null ||
+            gapBetweenItemsPx.value == null || viewportHeightPx.value == null ||
+            anchorType.value == null || reverseLayout.value == null ||
+            beforeContentPaddingPx.value == null || autoCentering.value == null ||
+            layoutInfo.internalVisibleItemInfo().isEmpty()
+        ) {
+            0
+        } else {
+            (layoutInfo.afterAutoCenteringPadding - gapBetweenItemsPx.value!!).coerceAtLeast(0)
+        }
+    }
+
+    /**
+     * The offset of the item closest to the viewport center. Depending on the
+     * [ScalingLazyListAnchorType] of the [ScalingLazyColumn] the offset will be relative to either
+     * the items Edge or Center.
+     *
+     * A positive value indicates that the center item's anchor point is above the viewport
+     * center-line, a negative value indicates that the center item anchor point is below the
+     * viewport center-line.
+     */
+    public val centerItemScrollOffset: Int
+        get() =
+            (layoutInfo as? DefaultScalingLazyListLayoutInfo)?.let {
+                if (it.initialized) it.centerItemScrollOffset else null
+            } ?: initialCenterItemScrollOffset
+
+    /**
+     * The object of [ScalingLazyListLayoutInfo] calculated during the last layout pass. For
+     * example, you can use it to calculate what items are currently visible.
+     */
+    public val layoutInfo: ScalingLazyListLayoutInfo by derivedStateOf {
+        if (extraPaddingPx.value == null || scalingParams.value == null ||
+            gapBetweenItemsPx.value == null || viewportHeightPx.value == null ||
+            anchorType.value == null || reverseLayout.value == null ||
+            beforeContentPaddingPx.value == null
+        ) {
+            EmptyScalingLazyListLayoutInfo
+        } else {
+            val visibleItemsInfo = mutableListOf<ScalingLazyListItemInfo>()
+            val viewportHeightPx = viewportHeightPx.value!!
+            var newCenterItemIndex = 0
+            var newCenterItemScrollOffset = 0
+            val visible = initialized.value || localInspectionMode.value
+
+            // The verticalAdjustment is used to allow for the extraPadding that the
+            // ScalingLazyColumn employs to ensure that there are sufficient list items composed
+            // by the underlying LazyList even when there is extreme scaling being applied that
+            // could result in additional list items be eligible to be drawn.
+            // It is important to adjust for this extra space when working out the viewport
+            // center-line based coordinate system of the ScalingLazyList.
+            val verticalAdjustment =
+                lazyListState.layoutInfo.viewportStartOffset + extraPaddingPx.value!!
+
+            // Find the item in the middle of the viewport
+            val centralItemArrayIndex =
+                findItemNearestCenter(verticalAdjustment)
+            if (centralItemArrayIndex != null) {
+                val originalVisibleItemsInfo = lazyListState.layoutInfo.visibleItemsInfo
+                val centralItem = originalVisibleItemsInfo[centralItemArrayIndex]
+
+                // Place the center item
+                val centerItemInfo: ScalingLazyListItemInfo = calculateItemInfo(
+                    centralItem.offset,
+                    centralItem,
+                    verticalAdjustment,
+                    viewportHeightPx,
+                    viewportCenterLinePx(),
+                    scalingParams.value!!,
+                    beforeContentPaddingPx.value!!,
+                    anchorType.value!!,
+                    autoCentering.value,
+                    visible
+                )
+                visibleItemsInfo.add(
+                    centerItemInfo
+                )
+
+                newCenterItemIndex = centerItemInfo.index
+                newCenterItemScrollOffset = -centerItemInfo.offset
+
+                // Find the adjusted position of the central item in the coordinate system of the
+                // underlying LazyColumn by adjusting for any scaling
+                val centralItemAdjustedUnderlyingOffset =
+                    centralItem.offset + ((centerItemInfo.startOffset(anchorType.value!!) -
+                        centerItemInfo.unadjustedStartOffset(anchorType.value!!))).roundToInt()
+
+                // Go Up
+                // nextItemBottomNoPadding uses the coordinate system of the underlying LazyList. It
+                // keeps track of the top of the next potential list item that is a candidate to be
+                // drawn in the viewport as we walk up the list items from the center. Going up
+                // involved making offset smaller/negative as the coordinate system of the LazyList
+                // starts at the top of the viewport. Note that the start of the lazy list
+                // coordinates starts at '- start content padding in pixels' and goes beyond the
+                // last visible list items to include the end content padding in pixels.
+
+                // centralItem.offset is a startOffset in the coordinate system of the
+                // underlying lazy list.
+                var nextItemBottomNoPadding =
+                    centralItemAdjustedUnderlyingOffset - gapBetweenItemsPx.value!!
+
+                (centralItemArrayIndex - 1 downTo 0).forEach { ix ->
+                    if (nextItemBottomNoPadding >= verticalAdjustment) {
+                        val currentItem =
+                            lazyListState.layoutInfo.visibleItemsInfo[ix]
+                        if (!discardAutoCenteringListItem(currentItem)) {
+                            val itemInfo = calculateItemInfo(
+                                nextItemBottomNoPadding - currentItem.size,
+                                currentItem,
+                                verticalAdjustment,
+                                viewportHeightPx,
+                                viewportCenterLinePx(),
+                                scalingParams.value!!,
+                                beforeContentPaddingPx.value!!,
+                                anchorType.value!!,
+                                autoCentering.value,
+                                visible
+                            )
+                            visibleItemsInfo.add(0, itemInfo)
+                            nextItemBottomNoPadding =
+                                nextItemBottomNoPadding - itemInfo.size - gapBetweenItemsPx.value!!
+                        }
+                    } else {
+                        return@forEach
+                    }
+                }
+
+                // Go Down
+                // nextItemTopNoPadding uses the coordinate system of the underlying LazyList. It
+                // keeps track of the top of the next potential list item that is a candidate to be
+                // drawn in the viewport as we walk down the list items from the center.
+                var nextItemTopNoPadding =
+                    centralItemAdjustedUnderlyingOffset + centerItemInfo.size +
+                        gapBetweenItemsPx.value!!
+
+                (((centralItemArrayIndex + 1) until
+                    originalVisibleItemsInfo.size)).forEach { ix ->
+                    if ((nextItemTopNoPadding - viewportHeightPx) <= verticalAdjustment) {
+                        val currentItem =
+                            lazyListState.layoutInfo.visibleItemsInfo[ix]
+                        if (!discardAutoCenteringListItem(currentItem)) {
+                            val itemInfo = calculateItemInfo(
+                                nextItemTopNoPadding,
+                                currentItem,
+                                verticalAdjustment,
+                                viewportHeightPx,
+                                viewportCenterLinePx(),
+                                scalingParams.value!!,
+                                beforeContentPaddingPx.value!!,
+                                anchorType.value!!,
+                                autoCentering.value,
+                                visible
+                            )
+
+                            visibleItemsInfo.add(itemInfo)
+                            nextItemTopNoPadding += itemInfo.size + gapBetweenItemsPx.value!!
+                        }
+                    } else {
+                        return@forEach
+                    }
+                }
+            }
+            val totalItemsCount =
+                if (autoCentering.value != null) {
+                    (lazyListState.layoutInfo.totalItemsCount - 2).coerceAtLeast(0)
+                } else {
+                    lazyListState.layoutInfo.totalItemsCount
+                }
+
+            // Decide if we are ready for the 2nd stage of initialization
+            // 1. We are not yet initialized and
+
+            val readyForInitialScroll =
+                if (! initialized.value) {
+                    // 1. autoCentering is off or
+                    // 2. The list has no items or
+                    // 3. the before content autoCentering Spacer has been sized.
+                    // NOTE: It is possible, if the first real item in the list is large, that the size
+                    // of the Spacer is 0.
+                    autoCentering.value == null || (
+                        lazyListState.layoutInfo.visibleItemsInfo.size >= 2 && (
+                            // or Empty list (other than the 2 spacers)
+                            lazyListState.layoutInfo.visibleItemsInfo.size == 2 ||
+                                // or first item is correctly size
+                                topSpacerIsCorrectlySized(
+                                    lazyListState.layoutInfo.visibleItemsInfo,
+                                    lazyListState.layoutInfo.totalItemsCount
+                                )
+                            )
+                        )
+                } else {
+                    // We are already initialized and have an incomplete scroll to finish
+                    incompleteScrollItem.value != null
+                }
+
+            DefaultScalingLazyListLayoutInfo(
+                internalVisibleItemsInfo = visibleItemsInfo,
+                totalItemsCount = totalItemsCount,
+                viewportStartOffset = lazyListState.layoutInfo.viewportStartOffset +
+                    extraPaddingPx.value!!,
+                viewportEndOffset = lazyListState.layoutInfo.viewportEndOffset -
+                    extraPaddingPx.value!!,
+                centerItemIndex = if (initialized.value) newCenterItemIndex else 0,
+                centerItemScrollOffset = if (initialized.value) newCenterItemScrollOffset else 0,
+                reverseLayout = reverseLayout.value!!,
+                orientation = lazyListState.layoutInfo.orientation,
+                viewportSize = IntSize(
+                    width = lazyListState.layoutInfo.viewportSize.width,
+                    height = lazyListState.layoutInfo.viewportSize.height -
+                        extraPaddingPx.value!! * 2
+                ),
+                beforeContentPadding = beforeContentPaddingPx.value!!,
+                afterContentPadding = afterContentPaddingPx.value!!,
+                beforeAutoCenteringPadding = calculateTopAutoCenteringPaddingPx(visibleItemsInfo,
+                    totalItemsCount),
+                afterAutoCenteringPadding = calculateBottomAutoCenteringPaddingPx(visibleItemsInfo,
+                    totalItemsCount),
+                readyForInitialScroll = readyForInitialScroll,
+                initialized = initialized.value,
+                anchorType = anchorType.value!!,
+            )
+        }
+    }
+
+    private fun findItemNearestCenter(
+        verticalAdjustment: Int
+    ): Int? {
+        var result: Int? = null
+        // Find the item in the middle of the viewport
+        for (i in lazyListState.layoutInfo.visibleItemsInfo.indices) {
+            val item = lazyListState.layoutInfo.visibleItemsInfo[i]
+            if (! discardAutoCenteringListItem(item)) {
+                val rawItemStart = item.offset - verticalAdjustment
+                val rawItemEnd = rawItemStart + item.size + gapBetweenItemsPx.value!! / 2f
+                result = i
+                if (rawItemEnd > viewportCenterLinePx()) {
+                    break
+                }
+            }
+        }
+        return result
+    }
+
+    companion object {
+        /**
+         * The default [Saver] implementation for [ScalingLazyListState].
+         */
+        val Saver = listSaver<ScalingLazyListState, Int>(
+            save = {
+                listOf(
+                    it.centerItemIndex,
+                    it.centerItemScrollOffset,
+                )
+            },
+            restore = {
+                val scalingLazyColumnState = ScalingLazyListState(it[0], it[1])
+                scalingLazyColumnState
+            }
+        )
+    }
+
+    override val isScrollInProgress: Boolean
+        get() {
+            return lazyListState.isScrollInProgress
+        }
+
+    override fun dispatchRawDelta(delta: Float): Float {
+        return lazyListState.dispatchRawDelta(delta)
+    }
+
+    override suspend fun scroll(
+        scrollPriority: MutatePriority,
+        block: suspend ScrollScope.() -> Unit
+    ) {
+        lazyListState.scroll(scrollPriority = scrollPriority, block = block)
+    }
+
+    override val canScrollForward: Boolean
+        get() = lazyListState.canScrollForward
+
+    override val canScrollBackward: Boolean
+        get() = lazyListState.canScrollBackward
+    /**
+     * Instantly brings the item at [index] to the center of the viewport and positions it based on
+     * the [anchorType] and applies the [scrollOffset] pixels.
+     *
+     * @param index the index to which to scroll. Must be non-negative.
+     * @param scrollOffset the offset that the item should end up after the scroll. Note that
+     * positive offset refers to forward scroll, so in a top-to-bottom list, positive offset will
+     * scroll the item further upward (taking it partly offscreen).
+     */
+    public suspend fun scrollToItem(
+        /*@IntRange(from = 0)*/
+        index: Int,
+        /*@IntRange(from = 0)*/
+        scrollOffset: Int = 0
+    ) {
+        return scrollToItem(false, index, scrollOffset)
+    }
+
+    /**
+     * Brings the item at [index] to the center of the viewport and positions it based on
+     * the [anchorType] and applies the [scrollOffset] pixels.
+     *
+     * @param animated whether to animate the scroll
+     * @param index the index to which to scroll. Must be non-negative.
+     * @param scrollOffset the offset that the item should end up after the scroll. Note that
+     * positive offset refers to forward scroll, so in a top-to-bottom list, positive offset will
+     * scroll the item further upward (taking it partly offscreen).
+     */
+    internal suspend fun scrollToItem(
+        animated: Boolean,
+        /*@IntRange(from = 0)*/
+        index: Int,
+        /*@IntRange(from = 0)*/
+        scrollOffset: Int,
+    ) {
+        if (!initialized.value) {
+            // We can't scroll yet, save to do it when we can (on the first composition).
+            initialCenterItemIndex = index
+            initialCenterItemScrollOffset = scrollOffset
+            return
+        }
+
+        // Find the underlying LazyList index taking into account the Spacer added before
+        // the first ScalingLazyColumn list item when autoCentering
+        val targetIndex = index.coerceAtMost(layoutInfo.totalItemsCount)
+        val lazyListStateIndex = targetIndex + if (autoCentering.value != null) 1 else 0
+
+        val offsetToCenterOfViewport =
+            beforeContentPaddingPx.value!! - viewportCenterLinePx()
+        if (anchorType.value == ScalingLazyListAnchorType.ItemStart) {
+            val offset = offsetToCenterOfViewport + scrollOffset
+            return lazyListState.scrollToItem(animated, lazyListStateIndex, offset)
+        } else {
+            var item = lazyListState.layoutInfo.findItemInfoWithIndex(lazyListStateIndex)
+            if (item == null) {
+                val estimatedUnadjustedHeight = layoutInfo.averageUnadjustedItemSize()
+                val estimatedOffset =
+                    offsetToCenterOfViewport + (estimatedUnadjustedHeight / 2) + scrollOffset
+
+                // Scroll the item into the middle of the viewport so that we know it is visible
+                lazyListState.scrollToItem(animated, lazyListStateIndex, estimatedOffset)
+                // Now we know that the item is visible find it and fine tune our position
+                item = lazyListState.layoutInfo.findItemInfoWithIndex(lazyListStateIndex)
+            }
+            if (item != null) {
+                // Decide if the item is in the right place
+                if (centerItemIndex != index || centerItemScrollOffset != scrollOffset) {
+                    val offset = offsetToCenterOfViewport + (item.size / 2) + scrollOffset
+                    return lazyListState.scrollToItem(animated, lazyListStateIndex, offset)
+                }
+            } else {
+                // The scroll has failed - this should only happen if the list is not currently
+                // visible
+                incompleteScrollItem.value = targetIndex
+                incompleteScrollOffset.value = scrollOffset
+                incompleteScrollAnimated.value = animated
+            }
+        }
+        return
+    }
+
+    internal suspend fun scrollToInitialItem() {
+        // First time initialization
+        if (!initialized.value) {
+            initialized.value = true
+            scrollToItem(initialCenterItemIndex, initialCenterItemScrollOffset)
+        }
+        // Check whether we are becoming visible after an incomplete scrollTo/animatedScrollTo
+        if (incompleteScrollItem.value != null) {
+            val animated = incompleteScrollAnimated.value
+            val targetIndex = incompleteScrollItem.value!!
+            val targetOffset = incompleteScrollOffset.value!!
+            // Reset the incomplete scroll indicator
+            incompleteScrollItem.value = null
+            scrollToItem(animated, targetIndex, targetOffset)
+        }
+        return
+    }
+
+    /**
+     * Animate (smooth scroll) the given item at [index] to the center of the viewport and position
+     * it based on the [anchorType] and applies the [scrollOffset] pixels.
+     *
+     * @param index the index to which to scroll. Must be non-negative.
+     * @param scrollOffset the offset that the item should end up after the scroll (same as
+     * [scrollToItem]) - note that positive offset refers to forward scroll, so in a
+     * top-to-bottom list, positive offset will scroll the item further upward (taking it partly
+     * offscreen)
+     */
+    public suspend fun animateScrollToItem(
+        /*@IntRange(from = 0)*/
+        index: Int,
+        /*@IntRange(from = 0)*/
+        scrollOffset: Int = 0
+    ) {
+        return scrollToItem(true, index, scrollOffset)
+    }
+
+    private fun discardAutoCenteringListItem(item: LazyListItemInfo): Boolean =
+        autoCentering.value != null &&
+            (item.index == 0 || item.index == lazyListState.layoutInfo.totalItemsCount - 1)
+
+    /**
+     * Calculate the amount of top padding needed (if any) to make sure that the
+     * [AutoCenteringParams.itemIndex] item can be placed in the center of the viewport at
+     * [AutoCenteringParams.itemOffset]
+     */
+    private fun calculateTopAutoCenteringPaddingPx(
+        visibleItems: List<ScalingLazyListItemInfo>,
+        totalItemCount: Int
+    ): Int {
+        if (autoCentering.value == null ||
+            (visibleItems.isNotEmpty() && visibleItems.first().index != 0)) return 0
+
+        // Work out the index we want to find - if there are less items in the list than would be
+        // needed to make initialItemIndex be visible then use the last visible item
+        val itemIndexToFind = autoCentering.value!!.itemIndex.coerceAtMost(totalItemCount - 1)
+
+        // Find the initialCenterItem, if it is null that means it is not in view - therefore
+        // we have more than enough content before it to make sure it can be scrolled to the center
+        // of the viewport
+        val initialCenterItem =
+            visibleItems.find { it.index == itemIndexToFind }
+
+        // Determine how much space we actually need
+        var spaceNeeded = spaceNeeded(initialCenterItem)
+
+        if (spaceNeeded > 0f) {
+            // Now see how much content we already have
+            visibleItems.map {
+                if (it.index < itemIndexToFind) {
+                    // Reduce the space needed
+                    spaceNeeded = spaceNeeded - gapBetweenItemsPx.value!! - it.unadjustedSize
+                }
+            }
+        }
+        return (spaceNeeded + gapBetweenItemsPx.value!!).coerceAtLeast(0)
+    }
+
+    /**
+     * Determine if the top Spacer component in the underlying LazyColumn has the correct size. We
+     * need to be sure that it has the correct size before we do scrollToInitialItem in order to
+     * make sure that the initial scroll will be successful.
+     */
+    private fun topSpacerIsCorrectlySized(
+        visibleItems: List<LazyListItemInfo>,
+        totalItemCount: Int
+    ): Boolean {
+        // If the top items has a non-zero size we know that it has been correctly inflated.
+        if (lazyListState.layoutInfo.visibleItemsInfo.first().size > 0) return true
+
+        // Work out the index we want to find - if there are less items in the list than would be
+        // needed to make initialItemIndex be visible then use the last visible item. The -2 is to
+        // allow for the spacers, i.e. an underlying list of size 3 has 2 spacers in index 0 and 2
+        // and one real item in underlying lazy column index 1.
+        val itemIndexToFind = (autoCentering.value!!.itemIndex + 1).coerceAtMost(totalItemCount - 2)
+
+        // Find the initialCenterItem, if it is null that means it is not in view - therefore
+        // we have more than enough content before it to make sure it can be scrolled to the center
+        // of the viewport
+        val initialCenterItem =
+            visibleItems.find { it.index == itemIndexToFind }
+
+        // Determine how much space we actually need
+        var spaceNeeded = spaceNeeded(initialCenterItem)
+
+        if (spaceNeeded > 0f) {
+            // Now see how much content we already have
+            visibleItems.map {
+                if (it.index != 0 && it.index < itemIndexToFind) {
+                    // Reduce the space needed
+                    spaceNeeded = spaceNeeded - gapBetweenItemsPx.value!! - it.size
+                }
+            }
+        }
+        // Finally if the remaining space needed is less that the gap between items then we do not
+        // need to add any additional space so the spacer being size zero is correct. Otherwise we
+        // need to wait for it to be inflated.
+        return spaceNeeded < gapBetweenItemsPx.value!!
+    }
+
+    private fun spaceNeeded(item: ScalingLazyListItemInfo?) =
+        viewportCenterLinePx() - gapBetweenItemsPx.value!! - autoCentering.value!!.itemOffset -
+            (item?.unadjustedItemSizeAboveOffsetPoint() ?: 0)
+
+    private fun spaceNeeded(item: LazyListItemInfo?) =
+        viewportCenterLinePx() - gapBetweenItemsPx.value!! - autoCentering.value!!.itemOffset -
+            (item?.itemSizeAboveOffsetPoint() ?: 0)
+
+    private fun calculateBottomAutoCenteringPaddingPx(
+        visibleItemsInfo: List<ScalingLazyListItemInfo>,
+        totalItemsCount: Int
+    ) = if (autoCentering.value != null && visibleItemsInfo.isNotEmpty() &&
+        visibleItemsInfo.last().index == totalItemsCount - 1
+    ) {
+        // Round any fractional part up for the bottom spacer as we round down toward zero
+        // for the viewport center line and item heights working from the top of the
+        // viewport and then add 1 pixel if needed (for an odd height viewport) at the end
+        // spacer
+        viewportHeightPx.value!! - viewportCenterLinePx() -
+            visibleItemsInfo.last().unadjustedItemSizeBelowOffsetPoint()
+    } else {
+        0
+    }
+
+    /**
+     * Calculate the center line of the viewport. This is half of the viewport height rounded down
+     * to the nearest int. This means that for a viewport with an odd number of pixels in height we
+     * will have the area above the viewport being one pixel smaller, e.g. a 199 pixel high
+     * viewport will be treated as having 99 pixels above and 100 pixels below the center line.
+     */
+    private fun viewportCenterLinePx(): Int = viewportHeightPx.value!! / 2
+
+    /**
+     * How much of the items unscaled size would be above the point on the item that represents the
+     * offset point. For an edge anchored item the offset point is the top of the item. For a center
+     * anchored item the offset point is floor(height/2).
+     */
+    private fun ScalingLazyListItemInfo.unadjustedItemSizeAboveOffsetPoint() =
+        if (anchorType.value == ScalingLazyListAnchorType.ItemStart) {
+            0
+        } else {
+            this.unadjustedSize / 2
+        }
+
+    /**
+     * How much of the items size would be above the point on the item that represents the
+     * offset point. For an edge anchored item the offset point is the top of the item. For a center
+     * anchored item the offset point is floor(height/2).
+     */
+    private fun LazyListItemInfo.itemSizeAboveOffsetPoint() =
+        if (anchorType.value == ScalingLazyListAnchorType.ItemStart) {
+            0
+        } else {
+            this.size / 2
+        }
+
+    /**
+     * How much of the items size would be below the point on the item that represents the
+     * offset point. For an edge anchored item the offset point is the top of the item. For a center
+     * anchored item the offset point is floor(height/2).
+     */
+    private fun ScalingLazyListItemInfo.unadjustedItemSizeBelowOffsetPoint() =
+        this.unadjustedSize - unadjustedItemSizeAboveOffsetPoint()
+}
+
+private fun LazyListLayoutInfo.findItemInfoWithIndex(index: Int): LazyListItemInfo? {
+    return this.visibleItemsInfo.find { it.index == index }
+}
+
+private suspend fun LazyListState.scrollToItem(animated: Boolean, index: Int, offset: Int) {
+    if (animated) animateScrollToItem(index, offset) else scrollToItem(index, offset)
+}
+
+private fun ScalingLazyListLayoutInfo.averageUnadjustedItemSize(): Int {
+    var totalSize = 0
+    visibleItemsInfo.forEach { totalSize += it.unadjustedSize }
+    return if (visibleItemsInfo.isNotEmpty())
+        (totalSize.toFloat() / visibleItemsInfo.size).roundToInt()
+    else 0
+}
+
+private object EmptyScalingLazyListLayoutInfo : ScalingLazyListLayoutInfo {
+    override val visibleItemsInfo = emptyList<ScalingLazyListItemInfo>()
+    override val viewportStartOffset = 0
+    override val viewportEndOffset = 0
+    override val totalItemsCount = 0
+    override val viewportSize = IntSize.Zero
+    override val orientation = Orientation.Vertical
+    override val reverseLayout = false
+    override val beforeContentPadding = 0
+    override val afterContentPadding = 0
+    override val beforeAutoCenteringPadding = 0
+    override val afterAutoCenteringPadding = 0
+    override val anchorType: ScalingLazyListAnchorType = ScalingLazyListAnchorType.ItemCenter
+}
+
+internal fun ScalingLazyListLayoutInfo.internalVisibleItemInfo() =
+    (this as? DefaultScalingLazyListLayoutInfo)?.internalVisibleItemsInfo ?: emptyList()
diff --git a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyScopeMarker.kt
similarity index 72%
copy from tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
copy to wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyScopeMarker.kt
index ebac64e..c0f85f7 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material/ExperimentalTvMaterialApi.kt
+++ b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/lazy/ScalingLazyScopeMarker.kt
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package androidx.tv.material
+package androidx.wear.compose.foundation.lazy
 
-@RequiresOptIn(
-    "This tv-material API is experimental and likely to change or be removed in the future."
-)
-@Retention(AnnotationRetention.BINARY)
-annotation class ExperimentalTvMaterialApi
\ No newline at end of file
+/**
+ * DSL marker used to distinguish between lazy layout scope and the item scope.
+ */
+@DslMarker
+annotation class ScalingLazyScopeMarker
diff --git a/wear/compose/compose-material/api/current.ignore b/wear/compose/compose-material/api/current.ignore
index 82c32a0..05e58f9 100644
--- a/wear/compose/compose-material/api/current.ignore
+++ b/wear/compose/compose-material/api/current.ignore
@@ -1,7 +1,3 @@
 // Baseline format: 1.0
 ChangedValue: androidx.wear.compose.material.TimeTextDefaults#TimeFormat12Hours:
     Field androidx.wear.compose.material.TimeTextDefaults.TimeFormat12Hours has changed value from h:mm a to h:mm
-
-
-InvalidNullConversion: androidx.wear.compose.material.PickerKt#Picker(androidx.wear.compose.material.PickerState, String, androidx.compose.ui.Modifier, boolean, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,? extends kotlin.Unit>, kotlin.jvm.functions.Function0<? extends kotlin.Unit>, androidx.wear.compose.material.ScalingParams, float, float, long, androidx.compose.foundation.gestures.FlingBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,? extends kotlin.Unit>) parameter #1:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter contentDescription in androidx.wear.compose.material.PickerKt.Picker(androidx.wear.compose.material.PickerState state, String contentDescription, androidx.compose.ui.Modifier modifier, boolean readOnly, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,? extends kotlin.Unit> readOnlyLabel, kotlin.jvm.functions.Function0<? extends kotlin.Unit> onSelected, androidx.wear.compose.material.ScalingParams scalingParams, float separation, float gradientRatio, long gradientColor, androidx.compose.foundation.gestures.FlingBehavior flingBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,? extends kotlin.Unit> option)
diff --git a/wear/compose/compose-material/api/current.txt b/wear/compose/compose-material/api/current.txt
index f728567..029a1e9 100644
--- a/wear/compose/compose-material/api/current.txt
+++ b/wear/compose/compose-material/api/current.txt
@@ -4,8 +4,8 @@
   public final class AnimationKt {
   }
 
-  @androidx.compose.runtime.Immutable public final class AutoCenteringParams {
-    ctor public AutoCenteringParams(optional int itemIndex, optional int itemOffset);
+  @Deprecated @androidx.compose.runtime.Immutable public final class AutoCenteringParams {
+    ctor @Deprecated public AutoCenteringParams(optional int itemIndex, optional int itemOffset);
   }
 
   @androidx.compose.runtime.Stable public interface ButtonBorder {
@@ -262,17 +262,19 @@
   }
 
   public final class PickerDefaults {
+    method public androidx.wear.compose.foundation.lazy.ScalingParams defaultScalingParams(optional float edgeScale, optional float edgeAlpha, optional float minElementHeight, optional float maxElementHeight, optional float minTransitionArea, optional float maxTransitionArea, optional androidx.compose.animation.core.Easing scaleInterpolator, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Constraints,java.lang.Integer> viewportVerticalOffsetResolver);
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.FlingBehavior flingBehavior(androidx.wear.compose.material.PickerState state, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decay);
     method public float getDefaultGradientRatio();
-    method public androidx.wear.compose.material.ScalingParams scalingParams(optional float edgeScale, optional float edgeAlpha, optional float minElementHeight, optional float maxElementHeight, optional float minTransitionArea, optional float maxTransitionArea, optional androidx.compose.animation.core.Easing scaleInterpolator, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Constraints,java.lang.Integer> viewportVerticalOffsetResolver);
+    method @Deprecated public androidx.wear.compose.material.ScalingParams scalingParams(optional float edgeScale, optional float edgeAlpha, optional float minElementHeight, optional float maxElementHeight, optional float minTransitionArea, optional float maxTransitionArea, optional androidx.compose.animation.core.Easing scaleInterpolator, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Constraints,java.lang.Integer> viewportVerticalOffsetResolver);
     property public final float DefaultGradientRatio;
     field public static final androidx.wear.compose.material.PickerDefaults INSTANCE;
   }
 
   public final class PickerKt {
-    method @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material.PickerState state, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional androidx.wear.compose.material.ScalingParams scalingParams, optional float separation, optional float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,kotlin.Unit> option);
+    method @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material.PickerState state, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional androidx.wear.compose.foundation.lazy.ScalingParams scalingParams, optional float separation, optional float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,kotlin.Unit> option);
     method @Deprecated @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material.PickerState state, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional androidx.wear.compose.material.ScalingParams scalingParams, optional float separation, optional float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,kotlin.Unit> option);
-    method @Deprecated @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material.PickerState state, String contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,? extends kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<? extends kotlin.Unit> onSelected, optional androidx.wear.compose.material.ScalingParams scalingParams, optional float separation, optional float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,? extends kotlin.Unit> option);
+    method @Deprecated @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material.PickerState state, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,? extends kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<? extends kotlin.Unit> onSelected, optional androidx.wear.compose.material.ScalingParams scalingParams, optional float separation, optional float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,? extends kotlin.Unit> option);
+    method @Deprecated @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material.PickerState state, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,? extends kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<? extends kotlin.Unit> onSelected, optional androidx.wear.compose.material.ScalingParams scalingParams, optional float separation, optional float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,? extends kotlin.Unit> option);
     method @androidx.compose.runtime.Composable public static androidx.wear.compose.material.PickerState rememberPickerState(int initialNumberOfOptions, optional int initiallySelectedOption, optional boolean repeatItems);
   }
 
@@ -325,7 +327,8 @@
 
   public final class PositionIndicatorKt {
     method @androidx.compose.runtime.Composable public static void PositionIndicator(androidx.compose.foundation.ScrollState scrollState, optional androidx.compose.ui.Modifier modifier, optional boolean reverseDirection);
-    method @androidx.compose.runtime.Composable public static void PositionIndicator(androidx.wear.compose.material.ScalingLazyListState scalingLazyListState, optional androidx.compose.ui.Modifier modifier, optional boolean reverseDirection);
+    method @androidx.compose.runtime.Composable public static void PositionIndicator(androidx.wear.compose.foundation.lazy.ScalingLazyListState scalingLazyListState, optional androidx.compose.ui.Modifier modifier, optional boolean reverseDirection);
+    method @Deprecated @androidx.compose.runtime.Composable public static void PositionIndicator(androidx.wear.compose.material.ScalingLazyListState scalingLazyListState, optional androidx.compose.ui.Modifier modifier, optional boolean reverseDirection);
     method @androidx.compose.runtime.Composable public static void PositionIndicator(androidx.compose.foundation.lazy.LazyListState lazyListState, optional androidx.compose.ui.Modifier modifier, optional boolean reverseDirection);
     method @androidx.compose.runtime.Composable public static void PositionIndicator(kotlin.jvm.functions.Function0<java.lang.Float> value, optional androidx.compose.ui.Modifier modifier, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> range, optional long color, optional boolean reverseDirection, optional int position);
     method @androidx.compose.runtime.Composable public static void PositionIndicator(androidx.wear.compose.material.PositionIndicatorState state, float indicatorHeight, float indicatorWidth, float paddingHorizontal, optional androidx.compose.ui.Modifier modifier, optional long background, optional long color, optional boolean reverseDirection, optional int position);
@@ -384,43 +387,43 @@
     method @androidx.compose.runtime.Composable public static void Scaffold(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? vignette, optional kotlin.jvm.functions.Function0<kotlin.Unit>? positionIndicator, optional kotlin.jvm.functions.Function0<kotlin.Unit>? pageIndicator, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
-  public final class ScalingLazyColumnDefaults {
-    method public androidx.wear.compose.material.ScalingParams scalingParams(optional float edgeScale, optional float edgeAlpha, optional float minElementHeight, optional float maxElementHeight, optional float minTransitionArea, optional float maxTransitionArea, optional androidx.compose.animation.core.Easing scaleInterpolator, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Constraints,java.lang.Integer> viewportVerticalOffsetResolver);
-    method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.FlingBehavior snapFlingBehavior(androidx.wear.compose.material.ScalingLazyListState state, optional float snapOffset, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decay);
-    field public static final androidx.wear.compose.material.ScalingLazyColumnDefaults INSTANCE;
+  @Deprecated public final class ScalingLazyColumnDefaults {
+    method @Deprecated public androidx.wear.compose.material.ScalingParams scalingParams(optional float edgeScale, optional float edgeAlpha, optional float minElementHeight, optional float maxElementHeight, optional float minTransitionArea, optional float maxTransitionArea, optional androidx.compose.animation.core.Easing scaleInterpolator, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Constraints,java.lang.Integer> viewportVerticalOffsetResolver);
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.FlingBehavior snapFlingBehavior(androidx.wear.compose.material.ScalingLazyListState state, optional float snapOffset, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decay);
+    field @Deprecated public static final androidx.wear.compose.material.ScalingLazyColumnDefaults INSTANCE;
   }
 
   public final class ScalingLazyColumnKt {
-    method @androidx.compose.runtime.Composable public static void ScalingLazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material.ScalingLazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.material.ScalingParams scalingParams, optional int anchorType, optional androidx.wear.compose.material.AutoCenteringParams? autoCentering, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material.ScalingLazyListScope,kotlin.Unit> content);
-    method public static inline <T> void items(androidx.wear.compose.material.ScalingLazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super T,kotlin.Unit> itemContent);
-    method public static inline <T> void items(androidx.wear.compose.material.ScalingLazyListScope, T![] items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super T,kotlin.Unit> itemContent);
-    method public static inline <T> void itemsIndexed(androidx.wear.compose.material.ScalingLazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
-    method public static inline <T> void itemsIndexed(androidx.wear.compose.material.ScalingLazyListScope, T![] items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method @Deprecated @androidx.compose.runtime.Composable public static void ScalingLazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material.ScalingLazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.material.ScalingParams scalingParams, optional int anchorType, optional androidx.wear.compose.material.AutoCenteringParams? autoCentering, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material.ScalingLazyListScope,kotlin.Unit> content);
+    method @Deprecated public static inline <T> void items(androidx.wear.compose.material.ScalingLazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super T,kotlin.Unit> itemContent);
+    method @Deprecated public static inline <T> void items(androidx.wear.compose.material.ScalingLazyListScope, T![] items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super T,kotlin.Unit> itemContent);
+    method @Deprecated public static inline <T> void itemsIndexed(androidx.wear.compose.material.ScalingLazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method @Deprecated public static inline <T> void itemsIndexed(androidx.wear.compose.material.ScalingLazyListScope, T![] items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
   }
 
   public final class ScalingLazyColumnMeasureKt {
   }
 
-  @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class ScalingLazyListAnchorType {
-    field public static final androidx.wear.compose.material.ScalingLazyListAnchorType.Companion Companion;
+  @Deprecated @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class ScalingLazyListAnchorType {
+    field @Deprecated public static final androidx.wear.compose.material.ScalingLazyListAnchorType.Companion Companion;
   }
 
-  public static final class ScalingLazyListAnchorType.Companion {
-    method public int getItemCenter();
-    method public int getItemStart();
+  @Deprecated public static final class ScalingLazyListAnchorType.Companion {
+    method @Deprecated public int getItemCenter();
+    method @Deprecated public int getItemStart();
     property public final int ItemCenter;
     property public final int ItemStart;
   }
 
-  public sealed interface ScalingLazyListItemInfo {
-    method public float getAlpha();
-    method public int getIndex();
-    method public Object getKey();
-    method public int getOffset();
-    method public float getScale();
-    method public int getSize();
-    method public int getUnadjustedOffset();
-    method public int getUnadjustedSize();
+  @Deprecated public sealed interface ScalingLazyListItemInfo {
+    method @Deprecated public float getAlpha();
+    method @Deprecated public int getIndex();
+    method @Deprecated public Object getKey();
+    method @Deprecated public int getOffset();
+    method @Deprecated public float getScale();
+    method @Deprecated public int getSize();
+    method @Deprecated public int getUnadjustedOffset();
+    method @Deprecated public int getUnadjustedSize();
     property public abstract float alpha;
     property public abstract int index;
     property public abstract Object key;
@@ -431,24 +434,24 @@
     property public abstract int unadjustedSize;
   }
 
-  @androidx.compose.runtime.Stable @androidx.wear.compose.material.ScalingLazyScopeMarker public sealed interface ScalingLazyListItemScope {
-    method public androidx.compose.ui.Modifier fillParentMaxHeight(androidx.compose.ui.Modifier, optional float fraction);
-    method public androidx.compose.ui.Modifier fillParentMaxSize(androidx.compose.ui.Modifier, optional float fraction);
-    method public androidx.compose.ui.Modifier fillParentMaxWidth(androidx.compose.ui.Modifier, optional float fraction);
+  @Deprecated @androidx.compose.runtime.Stable @androidx.wear.compose.material.ScalingLazyScopeMarker public sealed interface ScalingLazyListItemScope {
+    method @Deprecated public androidx.compose.ui.Modifier fillParentMaxHeight(androidx.compose.ui.Modifier, optional float fraction);
+    method @Deprecated public androidx.compose.ui.Modifier fillParentMaxSize(androidx.compose.ui.Modifier, optional float fraction);
+    method @Deprecated public androidx.compose.ui.Modifier fillParentMaxWidth(androidx.compose.ui.Modifier, optional float fraction);
   }
 
-  public sealed interface ScalingLazyListLayoutInfo {
-    method public int getAfterAutoCenteringPadding();
-    method public int getAfterContentPadding();
-    method public int getBeforeAutoCenteringPadding();
-    method public int getBeforeContentPadding();
-    method public androidx.compose.foundation.gestures.Orientation getOrientation();
-    method public boolean getReverseLayout();
-    method public int getTotalItemsCount();
-    method public int getViewportEndOffset();
-    method public long getViewportSize();
-    method public int getViewportStartOffset();
-    method public java.util.List<androidx.wear.compose.material.ScalingLazyListItemInfo> getVisibleItemsInfo();
+  @Deprecated public sealed interface ScalingLazyListLayoutInfo {
+    method @Deprecated public int getAfterAutoCenteringPadding();
+    method @Deprecated public int getAfterContentPadding();
+    method @Deprecated public int getBeforeAutoCenteringPadding();
+    method @Deprecated public int getBeforeContentPadding();
+    method @Deprecated public androidx.compose.foundation.gestures.Orientation getOrientation();
+    method @Deprecated public boolean getReverseLayout();
+    method @Deprecated public int getTotalItemsCount();
+    method @Deprecated public int getViewportEndOffset();
+    method @Deprecated public long getViewportSize();
+    method @Deprecated public int getViewportStartOffset();
+    method @Deprecated public java.util.List<androidx.wear.compose.material.ScalingLazyListItemInfo> getVisibleItemsInfo();
     property public abstract int afterAutoCenteringPadding;
     property public abstract int afterContentPadding;
     property public abstract int beforeAutoCenteringPadding;
@@ -462,51 +465,51 @@
     property public abstract java.util.List<androidx.wear.compose.material.ScalingLazyListItemInfo> visibleItemsInfo;
   }
 
-  @androidx.wear.compose.material.ScalingLazyScopeMarker public sealed interface ScalingLazyListScope {
-    method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material.ScalingLazyListItemScope,kotlin.Unit> content);
-    method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
+  @Deprecated @androidx.wear.compose.material.ScalingLazyScopeMarker public sealed interface ScalingLazyListScope {
+    method @Deprecated public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material.ScalingLazyListItemScope,kotlin.Unit> content);
+    method @Deprecated public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
   }
 
-  @androidx.compose.runtime.Stable public final class ScalingLazyListState implements androidx.compose.foundation.gestures.ScrollableState {
-    ctor public ScalingLazyListState(optional int initialCenterItemIndex, optional int initialCenterItemScrollOffset);
-    method public suspend Object? animateScrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public float dispatchRawDelta(float delta);
-    method public int getCenterItemIndex();
-    method public int getCenterItemScrollOffset();
-    method public androidx.wear.compose.material.ScalingLazyListLayoutInfo getLayoutInfo();
-    method public boolean isScrollInProgress();
-    method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+  @Deprecated @androidx.compose.runtime.Stable public final class ScalingLazyListState implements androidx.compose.foundation.gestures.ScrollableState {
+    ctor @Deprecated public ScalingLazyListState(optional int initialCenterItemIndex, optional int initialCenterItemScrollOffset);
+    method @Deprecated public suspend Object? animateScrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @Deprecated public float dispatchRawDelta(float delta);
+    method @Deprecated public int getCenterItemIndex();
+    method @Deprecated public int getCenterItemScrollOffset();
+    method @Deprecated public androidx.wear.compose.material.ScalingLazyListLayoutInfo getLayoutInfo();
+    method @Deprecated public boolean isScrollInProgress();
+    method @Deprecated public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @Deprecated public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
     property public boolean canScrollBackward;
     property public boolean canScrollForward;
     property public final int centerItemIndex;
     property public final int centerItemScrollOffset;
     property public boolean isScrollInProgress;
     property public final androidx.wear.compose.material.ScalingLazyListLayoutInfo layoutInfo;
-    field public static final androidx.wear.compose.material.ScalingLazyListState.Companion Companion;
+    field @Deprecated public static final androidx.wear.compose.material.ScalingLazyListState.Companion Companion;
   }
 
-  public static final class ScalingLazyListState.Companion {
-    method public androidx.compose.runtime.saveable.Saver<androidx.wear.compose.material.ScalingLazyListState,java.lang.Object> getSaver();
+  @Deprecated public static final class ScalingLazyListState.Companion {
+    method @Deprecated public androidx.compose.runtime.saveable.Saver<androidx.wear.compose.material.ScalingLazyListState,java.lang.Object> getSaver();
     property public final androidx.compose.runtime.saveable.Saver<androidx.wear.compose.material.ScalingLazyListState,java.lang.Object> Saver;
   }
 
   public final class ScalingLazyListStateKt {
-    method @androidx.compose.runtime.Composable public static androidx.wear.compose.material.ScalingLazyListState rememberScalingLazyListState(optional int initialCenterItemIndex, optional int initialCenterItemScrollOffset);
+    method @Deprecated @androidx.compose.runtime.Composable public static androidx.wear.compose.material.ScalingLazyListState rememberScalingLazyListState(optional int initialCenterItemIndex, optional int initialCenterItemScrollOffset);
   }
 
-  @kotlin.DslMarker public @interface ScalingLazyScopeMarker {
+  @Deprecated @kotlin.DslMarker public @interface ScalingLazyScopeMarker {
   }
 
-  @androidx.compose.runtime.Stable public interface ScalingParams {
-    method public float getEdgeAlpha();
-    method public float getEdgeScale();
-    method public float getMaxElementHeight();
-    method public float getMaxTransitionArea();
-    method public float getMinElementHeight();
-    method public float getMinTransitionArea();
-    method public androidx.compose.animation.core.Easing getScaleInterpolator();
-    method public int resolveViewportVerticalOffset(long viewportConstraints);
+  @Deprecated @androidx.compose.runtime.Stable public interface ScalingParams {
+    method @Deprecated public float getEdgeAlpha();
+    method @Deprecated public float getEdgeScale();
+    method @Deprecated public float getMaxElementHeight();
+    method @Deprecated public float getMaxTransitionArea();
+    method @Deprecated public float getMinElementHeight();
+    method @Deprecated public float getMinTransitionArea();
+    method @Deprecated public androidx.compose.animation.core.Easing getScaleInterpolator();
+    method @Deprecated public int resolveViewportVerticalOffset(long viewportConstraints);
     property public abstract float edgeAlpha;
     property public abstract float edgeScale;
     property public abstract float maxElementHeight;
@@ -519,7 +522,8 @@
   public final class ScrollAwayKt {
     method public static androidx.compose.ui.Modifier scrollAway(androidx.compose.ui.Modifier, androidx.compose.foundation.ScrollState scrollState, optional float offset);
     method public static androidx.compose.ui.Modifier scrollAway(androidx.compose.ui.Modifier, androidx.compose.foundation.lazy.LazyListState scrollState, optional int itemIndex, optional float offset);
-    method public static androidx.compose.ui.Modifier scrollAway(androidx.compose.ui.Modifier, androidx.wear.compose.material.ScalingLazyListState scrollState, optional int itemIndex, optional float offset);
+    method public static androidx.compose.ui.Modifier scrollAway(androidx.compose.ui.Modifier, androidx.wear.compose.foundation.lazy.ScalingLazyListState scrollState, optional int itemIndex, optional float offset);
+    method @Deprecated public static androidx.compose.ui.Modifier scrollAway(androidx.compose.ui.Modifier, androidx.wear.compose.material.ScalingLazyListState scrollState, optional int itemIndex, optional float offset);
   }
 
   @androidx.compose.runtime.Immutable public final class Shapes {
@@ -783,13 +787,17 @@
   }
 
   public final class DialogKt {
-    method @androidx.compose.runtime.Composable public static void Alert(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> negativeButton, kotlin.jvm.functions.Function0<kotlin.Unit> positiveButton, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? icon, optional androidx.wear.compose.material.ScalingLazyListState scrollState, optional long backgroundColor, optional long contentColor, optional long titleColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? content);
-    method @androidx.compose.runtime.Composable public static void Alert(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? icon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? message, optional androidx.wear.compose.material.ScalingLazyListState scrollState, optional long backgroundColor, optional long titleColor, optional long messageColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material.ScalingLazyListScope,kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable public static void Confirmation(kotlin.jvm.functions.Function0<kotlin.Unit> onTimeout, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? icon, optional androidx.wear.compose.material.ScalingLazyListState scrollState, optional long durationMillis, optional long backgroundColor, optional long contentColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void Alert(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> negativeButton, kotlin.jvm.functions.Function0<kotlin.Unit> positiveButton, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? icon, optional androidx.wear.compose.foundation.lazy.ScalingLazyListState scrollState, optional long backgroundColor, optional long contentColor, optional long titleColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? content);
+    method @androidx.compose.runtime.Composable public static void Alert(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? icon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? message, optional androidx.wear.compose.foundation.lazy.ScalingLazyListState scrollState, optional long backgroundColor, optional long titleColor, optional long messageColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit> content);
+    method @Deprecated @androidx.compose.runtime.Composable public static void Alert(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit> title, kotlin.jvm.functions.Function0<? extends kotlin.Unit> negativeButton, kotlin.jvm.functions.Function0<? extends kotlin.Unit> positiveButton, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit>? icon, optional androidx.wear.compose.material.ScalingLazyListState scrollState, optional long backgroundColor, optional long contentColor, optional long titleColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit>? content);
+    method @Deprecated @androidx.compose.runtime.Composable public static void Alert(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit>? icon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit>? message, optional androidx.wear.compose.material.ScalingLazyListState scrollState, optional long backgroundColor, optional long titleColor, optional long messageColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material.ScalingLazyListScope,? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void Confirmation(kotlin.jvm.functions.Function0<kotlin.Unit> onTimeout, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? icon, optional androidx.wear.compose.foundation.lazy.ScalingLazyListState scrollState, optional long durationMillis, optional long backgroundColor, optional long contentColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @Deprecated @androidx.compose.runtime.Composable public static void Confirmation(kotlin.jvm.functions.Function0<? extends kotlin.Unit> onTimeout, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit>? icon, optional androidx.wear.compose.material.ScalingLazyListState scrollState, optional long durationMillis, optional long backgroundColor, optional long contentColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit> content);
   }
 
   public final class Dialog_androidKt {
-    method @androidx.compose.runtime.Composable public static void Dialog(boolean showDialog, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material.ScalingLazyListState? scrollState, optional androidx.compose.ui.window.DialogProperties properties, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void Dialog(boolean showDialog, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.foundation.lazy.ScalingLazyListState? scrollState, optional androidx.compose.ui.window.DialogProperties properties, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @Deprecated @androidx.compose.runtime.Composable public static void Dialog(boolean showDialog, kotlin.jvm.functions.Function0<? extends kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material.ScalingLazyListState? scrollState, optional androidx.compose.ui.window.DialogProperties properties, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
   }
 
 }
diff --git a/wear/compose/compose-material/api/public_plus_experimental_current.txt b/wear/compose/compose-material/api/public_plus_experimental_current.txt
index 09bb7fd..244f8e6 100644
--- a/wear/compose/compose-material/api/public_plus_experimental_current.txt
+++ b/wear/compose/compose-material/api/public_plus_experimental_current.txt
@@ -4,8 +4,8 @@
   public final class AnimationKt {
   }
 
-  @androidx.compose.runtime.Immutable public final class AutoCenteringParams {
-    ctor public AutoCenteringParams(optional int itemIndex, optional int itemOffset);
+  @Deprecated @androidx.compose.runtime.Immutable public final class AutoCenteringParams {
+    ctor @Deprecated public AutoCenteringParams(optional int itemIndex, optional int itemOffset);
   }
 
   @androidx.compose.runtime.Stable public interface ButtonBorder {
@@ -278,17 +278,19 @@
   }
 
   public final class PickerDefaults {
+    method public androidx.wear.compose.foundation.lazy.ScalingParams defaultScalingParams(optional float edgeScale, optional float edgeAlpha, optional float minElementHeight, optional float maxElementHeight, optional float minTransitionArea, optional float maxTransitionArea, optional androidx.compose.animation.core.Easing scaleInterpolator, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Constraints,java.lang.Integer> viewportVerticalOffsetResolver);
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.FlingBehavior flingBehavior(androidx.wear.compose.material.PickerState state, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decay);
     method public float getDefaultGradientRatio();
-    method public androidx.wear.compose.material.ScalingParams scalingParams(optional float edgeScale, optional float edgeAlpha, optional float minElementHeight, optional float maxElementHeight, optional float minTransitionArea, optional float maxTransitionArea, optional androidx.compose.animation.core.Easing scaleInterpolator, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Constraints,java.lang.Integer> viewportVerticalOffsetResolver);
+    method @Deprecated public androidx.wear.compose.material.ScalingParams scalingParams(optional float edgeScale, optional float edgeAlpha, optional float minElementHeight, optional float maxElementHeight, optional float minTransitionArea, optional float maxTransitionArea, optional androidx.compose.animation.core.Easing scaleInterpolator, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Constraints,java.lang.Integer> viewportVerticalOffsetResolver);
     property public final float DefaultGradientRatio;
     field public static final androidx.wear.compose.material.PickerDefaults INSTANCE;
   }
 
   public final class PickerKt {
-    method @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material.PickerState state, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional androidx.wear.compose.material.ScalingParams scalingParams, optional float separation, optional float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,kotlin.Unit> option);
+    method @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material.PickerState state, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional androidx.wear.compose.foundation.lazy.ScalingParams scalingParams, optional float separation, optional float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,kotlin.Unit> option);
     method @Deprecated @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material.PickerState state, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional androidx.wear.compose.material.ScalingParams scalingParams, optional float separation, optional float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,kotlin.Unit> option);
-    method @Deprecated @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material.PickerState state, String contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,? extends kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<? extends kotlin.Unit> onSelected, optional androidx.wear.compose.material.ScalingParams scalingParams, optional float separation, optional float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,? extends kotlin.Unit> option);
+    method @Deprecated @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material.PickerState state, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,? extends kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<? extends kotlin.Unit> onSelected, optional androidx.wear.compose.material.ScalingParams scalingParams, optional float separation, optional float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,? extends kotlin.Unit> option);
+    method @Deprecated @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material.PickerState state, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,? extends kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<? extends kotlin.Unit> onSelected, optional androidx.wear.compose.material.ScalingParams scalingParams, optional float separation, optional float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,? extends kotlin.Unit> option);
     method @androidx.compose.runtime.Composable public static androidx.wear.compose.material.PickerState rememberPickerState(int initialNumberOfOptions, optional int initiallySelectedOption, optional boolean repeatItems);
   }
 
@@ -362,7 +364,8 @@
 
   public final class PositionIndicatorKt {
     method @androidx.compose.runtime.Composable public static void PositionIndicator(androidx.compose.foundation.ScrollState scrollState, optional androidx.compose.ui.Modifier modifier, optional boolean reverseDirection);
-    method @androidx.compose.runtime.Composable public static void PositionIndicator(androidx.wear.compose.material.ScalingLazyListState scalingLazyListState, optional androidx.compose.ui.Modifier modifier, optional boolean reverseDirection);
+    method @androidx.compose.runtime.Composable public static void PositionIndicator(androidx.wear.compose.foundation.lazy.ScalingLazyListState scalingLazyListState, optional androidx.compose.ui.Modifier modifier, optional boolean reverseDirection);
+    method @Deprecated @androidx.compose.runtime.Composable public static void PositionIndicator(androidx.wear.compose.material.ScalingLazyListState scalingLazyListState, optional androidx.compose.ui.Modifier modifier, optional boolean reverseDirection);
     method @androidx.compose.runtime.Composable public static void PositionIndicator(androidx.compose.foundation.lazy.LazyListState lazyListState, optional androidx.compose.ui.Modifier modifier, optional boolean reverseDirection);
     method @androidx.compose.runtime.Composable public static void PositionIndicator(kotlin.jvm.functions.Function0<java.lang.Float> value, optional androidx.compose.ui.Modifier modifier, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> range, optional long color, optional boolean reverseDirection, optional int position);
     method @androidx.compose.runtime.Composable public static void PositionIndicator(androidx.wear.compose.material.PositionIndicatorState state, float indicatorHeight, float indicatorWidth, float paddingHorizontal, optional androidx.compose.ui.Modifier modifier, optional long background, optional long color, optional boolean reverseDirection, optional int position);
@@ -432,43 +435,43 @@
     method @androidx.compose.runtime.Composable public static void Scaffold(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? vignette, optional kotlin.jvm.functions.Function0<kotlin.Unit>? positionIndicator, optional kotlin.jvm.functions.Function0<kotlin.Unit>? pageIndicator, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
-  public final class ScalingLazyColumnDefaults {
-    method public androidx.wear.compose.material.ScalingParams scalingParams(optional float edgeScale, optional float edgeAlpha, optional float minElementHeight, optional float maxElementHeight, optional float minTransitionArea, optional float maxTransitionArea, optional androidx.compose.animation.core.Easing scaleInterpolator, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Constraints,java.lang.Integer> viewportVerticalOffsetResolver);
-    method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.FlingBehavior snapFlingBehavior(androidx.wear.compose.material.ScalingLazyListState state, optional float snapOffset, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decay);
-    field public static final androidx.wear.compose.material.ScalingLazyColumnDefaults INSTANCE;
+  @Deprecated public final class ScalingLazyColumnDefaults {
+    method @Deprecated public androidx.wear.compose.material.ScalingParams scalingParams(optional float edgeScale, optional float edgeAlpha, optional float minElementHeight, optional float maxElementHeight, optional float minTransitionArea, optional float maxTransitionArea, optional androidx.compose.animation.core.Easing scaleInterpolator, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Constraints,java.lang.Integer> viewportVerticalOffsetResolver);
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.FlingBehavior snapFlingBehavior(androidx.wear.compose.material.ScalingLazyListState state, optional float snapOffset, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decay);
+    field @Deprecated public static final androidx.wear.compose.material.ScalingLazyColumnDefaults INSTANCE;
   }
 
   public final class ScalingLazyColumnKt {
-    method @androidx.compose.runtime.Composable public static void ScalingLazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material.ScalingLazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.material.ScalingParams scalingParams, optional int anchorType, optional androidx.wear.compose.material.AutoCenteringParams? autoCentering, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material.ScalingLazyListScope,kotlin.Unit> content);
-    method public static inline <T> void items(androidx.wear.compose.material.ScalingLazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super T,kotlin.Unit> itemContent);
-    method public static inline <T> void items(androidx.wear.compose.material.ScalingLazyListScope, T![] items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super T,kotlin.Unit> itemContent);
-    method public static inline <T> void itemsIndexed(androidx.wear.compose.material.ScalingLazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
-    method public static inline <T> void itemsIndexed(androidx.wear.compose.material.ScalingLazyListScope, T![] items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method @Deprecated @androidx.compose.runtime.Composable public static void ScalingLazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material.ScalingLazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.material.ScalingParams scalingParams, optional int anchorType, optional androidx.wear.compose.material.AutoCenteringParams? autoCentering, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material.ScalingLazyListScope,kotlin.Unit> content);
+    method @Deprecated public static inline <T> void items(androidx.wear.compose.material.ScalingLazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super T,kotlin.Unit> itemContent);
+    method @Deprecated public static inline <T> void items(androidx.wear.compose.material.ScalingLazyListScope, T![] items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super T,kotlin.Unit> itemContent);
+    method @Deprecated public static inline <T> void itemsIndexed(androidx.wear.compose.material.ScalingLazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method @Deprecated public static inline <T> void itemsIndexed(androidx.wear.compose.material.ScalingLazyListScope, T![] items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
   }
 
   public final class ScalingLazyColumnMeasureKt {
   }
 
-  @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class ScalingLazyListAnchorType {
-    field public static final androidx.wear.compose.material.ScalingLazyListAnchorType.Companion Companion;
+  @Deprecated @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class ScalingLazyListAnchorType {
+    field @Deprecated public static final androidx.wear.compose.material.ScalingLazyListAnchorType.Companion Companion;
   }
 
-  public static final class ScalingLazyListAnchorType.Companion {
-    method public int getItemCenter();
-    method public int getItemStart();
+  @Deprecated public static final class ScalingLazyListAnchorType.Companion {
+    method @Deprecated public int getItemCenter();
+    method @Deprecated public int getItemStart();
     property public final int ItemCenter;
     property public final int ItemStart;
   }
 
-  public sealed interface ScalingLazyListItemInfo {
-    method public float getAlpha();
-    method public int getIndex();
-    method public Object getKey();
-    method public int getOffset();
-    method public float getScale();
-    method public int getSize();
-    method public int getUnadjustedOffset();
-    method public int getUnadjustedSize();
+  @Deprecated public sealed interface ScalingLazyListItemInfo {
+    method @Deprecated public float getAlpha();
+    method @Deprecated public int getIndex();
+    method @Deprecated public Object getKey();
+    method @Deprecated public int getOffset();
+    method @Deprecated public float getScale();
+    method @Deprecated public int getSize();
+    method @Deprecated public int getUnadjustedOffset();
+    method @Deprecated public int getUnadjustedSize();
     property public abstract float alpha;
     property public abstract int index;
     property public abstract Object key;
@@ -479,24 +482,24 @@
     property public abstract int unadjustedSize;
   }
 
-  @androidx.compose.runtime.Stable @androidx.wear.compose.material.ScalingLazyScopeMarker public sealed interface ScalingLazyListItemScope {
-    method public androidx.compose.ui.Modifier fillParentMaxHeight(androidx.compose.ui.Modifier, optional float fraction);
-    method public androidx.compose.ui.Modifier fillParentMaxSize(androidx.compose.ui.Modifier, optional float fraction);
-    method public androidx.compose.ui.Modifier fillParentMaxWidth(androidx.compose.ui.Modifier, optional float fraction);
+  @Deprecated @androidx.compose.runtime.Stable @androidx.wear.compose.material.ScalingLazyScopeMarker public sealed interface ScalingLazyListItemScope {
+    method @Deprecated public androidx.compose.ui.Modifier fillParentMaxHeight(androidx.compose.ui.Modifier, optional float fraction);
+    method @Deprecated public androidx.compose.ui.Modifier fillParentMaxSize(androidx.compose.ui.Modifier, optional float fraction);
+    method @Deprecated public androidx.compose.ui.Modifier fillParentMaxWidth(androidx.compose.ui.Modifier, optional float fraction);
   }
 
-  public sealed interface ScalingLazyListLayoutInfo {
-    method public int getAfterAutoCenteringPadding();
-    method public int getAfterContentPadding();
-    method public int getBeforeAutoCenteringPadding();
-    method public int getBeforeContentPadding();
-    method public androidx.compose.foundation.gestures.Orientation getOrientation();
-    method public boolean getReverseLayout();
-    method public int getTotalItemsCount();
-    method public int getViewportEndOffset();
-    method public long getViewportSize();
-    method public int getViewportStartOffset();
-    method public java.util.List<androidx.wear.compose.material.ScalingLazyListItemInfo> getVisibleItemsInfo();
+  @Deprecated public sealed interface ScalingLazyListLayoutInfo {
+    method @Deprecated public int getAfterAutoCenteringPadding();
+    method @Deprecated public int getAfterContentPadding();
+    method @Deprecated public int getBeforeAutoCenteringPadding();
+    method @Deprecated public int getBeforeContentPadding();
+    method @Deprecated public androidx.compose.foundation.gestures.Orientation getOrientation();
+    method @Deprecated public boolean getReverseLayout();
+    method @Deprecated public int getTotalItemsCount();
+    method @Deprecated public int getViewportEndOffset();
+    method @Deprecated public long getViewportSize();
+    method @Deprecated public int getViewportStartOffset();
+    method @Deprecated public java.util.List<androidx.wear.compose.material.ScalingLazyListItemInfo> getVisibleItemsInfo();
     property public abstract int afterAutoCenteringPadding;
     property public abstract int afterContentPadding;
     property public abstract int beforeAutoCenteringPadding;
@@ -510,51 +513,51 @@
     property public abstract java.util.List<androidx.wear.compose.material.ScalingLazyListItemInfo> visibleItemsInfo;
   }
 
-  @androidx.wear.compose.material.ScalingLazyScopeMarker public sealed interface ScalingLazyListScope {
-    method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material.ScalingLazyListItemScope,kotlin.Unit> content);
-    method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
+  @Deprecated @androidx.wear.compose.material.ScalingLazyScopeMarker public sealed interface ScalingLazyListScope {
+    method @Deprecated public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material.ScalingLazyListItemScope,kotlin.Unit> content);
+    method @Deprecated public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
   }
 
-  @androidx.compose.runtime.Stable public final class ScalingLazyListState implements androidx.compose.foundation.gestures.ScrollableState {
-    ctor public ScalingLazyListState(optional int initialCenterItemIndex, optional int initialCenterItemScrollOffset);
-    method public suspend Object? animateScrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public float dispatchRawDelta(float delta);
-    method public int getCenterItemIndex();
-    method public int getCenterItemScrollOffset();
-    method public androidx.wear.compose.material.ScalingLazyListLayoutInfo getLayoutInfo();
-    method public boolean isScrollInProgress();
-    method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+  @Deprecated @androidx.compose.runtime.Stable public final class ScalingLazyListState implements androidx.compose.foundation.gestures.ScrollableState {
+    ctor @Deprecated public ScalingLazyListState(optional int initialCenterItemIndex, optional int initialCenterItemScrollOffset);
+    method @Deprecated public suspend Object? animateScrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @Deprecated public float dispatchRawDelta(float delta);
+    method @Deprecated public int getCenterItemIndex();
+    method @Deprecated public int getCenterItemScrollOffset();
+    method @Deprecated public androidx.wear.compose.material.ScalingLazyListLayoutInfo getLayoutInfo();
+    method @Deprecated public boolean isScrollInProgress();
+    method @Deprecated public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @Deprecated public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
     property public boolean canScrollBackward;
     property public boolean canScrollForward;
     property public final int centerItemIndex;
     property public final int centerItemScrollOffset;
     property public boolean isScrollInProgress;
     property public final androidx.wear.compose.material.ScalingLazyListLayoutInfo layoutInfo;
-    field public static final androidx.wear.compose.material.ScalingLazyListState.Companion Companion;
+    field @Deprecated public static final androidx.wear.compose.material.ScalingLazyListState.Companion Companion;
   }
 
-  public static final class ScalingLazyListState.Companion {
-    method public androidx.compose.runtime.saveable.Saver<androidx.wear.compose.material.ScalingLazyListState,java.lang.Object> getSaver();
+  @Deprecated public static final class ScalingLazyListState.Companion {
+    method @Deprecated public androidx.compose.runtime.saveable.Saver<androidx.wear.compose.material.ScalingLazyListState,java.lang.Object> getSaver();
     property public final androidx.compose.runtime.saveable.Saver<androidx.wear.compose.material.ScalingLazyListState,java.lang.Object> Saver;
   }
 
   public final class ScalingLazyListStateKt {
-    method @androidx.compose.runtime.Composable public static androidx.wear.compose.material.ScalingLazyListState rememberScalingLazyListState(optional int initialCenterItemIndex, optional int initialCenterItemScrollOffset);
+    method @Deprecated @androidx.compose.runtime.Composable public static androidx.wear.compose.material.ScalingLazyListState rememberScalingLazyListState(optional int initialCenterItemIndex, optional int initialCenterItemScrollOffset);
   }
 
-  @kotlin.DslMarker public @interface ScalingLazyScopeMarker {
+  @Deprecated @kotlin.DslMarker public @interface ScalingLazyScopeMarker {
   }
 
-  @androidx.compose.runtime.Stable public interface ScalingParams {
-    method public float getEdgeAlpha();
-    method public float getEdgeScale();
-    method public float getMaxElementHeight();
-    method public float getMaxTransitionArea();
-    method public float getMinElementHeight();
-    method public float getMinTransitionArea();
-    method public androidx.compose.animation.core.Easing getScaleInterpolator();
-    method public int resolveViewportVerticalOffset(long viewportConstraints);
+  @Deprecated @androidx.compose.runtime.Stable public interface ScalingParams {
+    method @Deprecated public float getEdgeAlpha();
+    method @Deprecated public float getEdgeScale();
+    method @Deprecated public float getMaxElementHeight();
+    method @Deprecated public float getMaxTransitionArea();
+    method @Deprecated public float getMinElementHeight();
+    method @Deprecated public float getMinTransitionArea();
+    method @Deprecated public androidx.compose.animation.core.Easing getScaleInterpolator();
+    method @Deprecated public int resolveViewportVerticalOffset(long viewportConstraints);
     property public abstract float edgeAlpha;
     property public abstract float edgeScale;
     property public abstract float maxElementHeight;
@@ -567,7 +570,8 @@
   public final class ScrollAwayKt {
     method public static androidx.compose.ui.Modifier scrollAway(androidx.compose.ui.Modifier, androidx.compose.foundation.ScrollState scrollState, optional float offset);
     method public static androidx.compose.ui.Modifier scrollAway(androidx.compose.ui.Modifier, androidx.compose.foundation.lazy.LazyListState scrollState, optional int itemIndex, optional float offset);
-    method public static androidx.compose.ui.Modifier scrollAway(androidx.compose.ui.Modifier, androidx.wear.compose.material.ScalingLazyListState scrollState, optional int itemIndex, optional float offset);
+    method public static androidx.compose.ui.Modifier scrollAway(androidx.compose.ui.Modifier, androidx.wear.compose.foundation.lazy.ScalingLazyListState scrollState, optional int itemIndex, optional float offset);
+    method @Deprecated public static androidx.compose.ui.Modifier scrollAway(androidx.compose.ui.Modifier, androidx.wear.compose.material.ScalingLazyListState scrollState, optional int itemIndex, optional float offset);
   }
 
   @androidx.compose.runtime.Immutable public final class Shapes {
@@ -884,13 +888,17 @@
   }
 
   public final class DialogKt {
-    method @androidx.compose.runtime.Composable public static void Alert(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> negativeButton, kotlin.jvm.functions.Function0<kotlin.Unit> positiveButton, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? icon, optional androidx.wear.compose.material.ScalingLazyListState scrollState, optional long backgroundColor, optional long contentColor, optional long titleColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? content);
-    method @androidx.compose.runtime.Composable public static void Alert(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? icon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? message, optional androidx.wear.compose.material.ScalingLazyListState scrollState, optional long backgroundColor, optional long titleColor, optional long messageColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material.ScalingLazyListScope,kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable public static void Confirmation(kotlin.jvm.functions.Function0<kotlin.Unit> onTimeout, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? icon, optional androidx.wear.compose.material.ScalingLazyListState scrollState, optional long durationMillis, optional long backgroundColor, optional long contentColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void Alert(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> negativeButton, kotlin.jvm.functions.Function0<kotlin.Unit> positiveButton, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? icon, optional androidx.wear.compose.foundation.lazy.ScalingLazyListState scrollState, optional long backgroundColor, optional long contentColor, optional long titleColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? content);
+    method @androidx.compose.runtime.Composable public static void Alert(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? icon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? message, optional androidx.wear.compose.foundation.lazy.ScalingLazyListState scrollState, optional long backgroundColor, optional long titleColor, optional long messageColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit> content);
+    method @Deprecated @androidx.compose.runtime.Composable public static void Alert(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit> title, kotlin.jvm.functions.Function0<? extends kotlin.Unit> negativeButton, kotlin.jvm.functions.Function0<? extends kotlin.Unit> positiveButton, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit>? icon, optional androidx.wear.compose.material.ScalingLazyListState scrollState, optional long backgroundColor, optional long contentColor, optional long titleColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit>? content);
+    method @Deprecated @androidx.compose.runtime.Composable public static void Alert(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit>? icon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit>? message, optional androidx.wear.compose.material.ScalingLazyListState scrollState, optional long backgroundColor, optional long titleColor, optional long messageColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material.ScalingLazyListScope,? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void Confirmation(kotlin.jvm.functions.Function0<kotlin.Unit> onTimeout, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? icon, optional androidx.wear.compose.foundation.lazy.ScalingLazyListState scrollState, optional long durationMillis, optional long backgroundColor, optional long contentColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @Deprecated @androidx.compose.runtime.Composable public static void Confirmation(kotlin.jvm.functions.Function0<? extends kotlin.Unit> onTimeout, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit>? icon, optional androidx.wear.compose.material.ScalingLazyListState scrollState, optional long durationMillis, optional long backgroundColor, optional long contentColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit> content);
   }
 
   public final class Dialog_androidKt {
-    method @androidx.compose.runtime.Composable public static void Dialog(boolean showDialog, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material.ScalingLazyListState? scrollState, optional androidx.compose.ui.window.DialogProperties properties, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void Dialog(boolean showDialog, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.foundation.lazy.ScalingLazyListState? scrollState, optional androidx.compose.ui.window.DialogProperties properties, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @Deprecated @androidx.compose.runtime.Composable public static void Dialog(boolean showDialog, kotlin.jvm.functions.Function0<? extends kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material.ScalingLazyListState? scrollState, optional androidx.compose.ui.window.DialogProperties properties, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
   }
 
 }
diff --git a/wear/compose/compose-material/api/restricted_current.ignore b/wear/compose/compose-material/api/restricted_current.ignore
index 82c32a0..05e58f9 100644
--- a/wear/compose/compose-material/api/restricted_current.ignore
+++ b/wear/compose/compose-material/api/restricted_current.ignore
@@ -1,7 +1,3 @@
 // Baseline format: 1.0
 ChangedValue: androidx.wear.compose.material.TimeTextDefaults#TimeFormat12Hours:
     Field androidx.wear.compose.material.TimeTextDefaults.TimeFormat12Hours has changed value from h:mm a to h:mm
-
-
-InvalidNullConversion: androidx.wear.compose.material.PickerKt#Picker(androidx.wear.compose.material.PickerState, String, androidx.compose.ui.Modifier, boolean, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,? extends kotlin.Unit>, kotlin.jvm.functions.Function0<? extends kotlin.Unit>, androidx.wear.compose.material.ScalingParams, float, float, long, androidx.compose.foundation.gestures.FlingBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,? extends kotlin.Unit>) parameter #1:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter contentDescription in androidx.wear.compose.material.PickerKt.Picker(androidx.wear.compose.material.PickerState state, String contentDescription, androidx.compose.ui.Modifier modifier, boolean readOnly, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,? extends kotlin.Unit> readOnlyLabel, kotlin.jvm.functions.Function0<? extends kotlin.Unit> onSelected, androidx.wear.compose.material.ScalingParams scalingParams, float separation, float gradientRatio, long gradientColor, androidx.compose.foundation.gestures.FlingBehavior flingBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,? extends kotlin.Unit> option)
diff --git a/wear/compose/compose-material/api/restricted_current.txt b/wear/compose/compose-material/api/restricted_current.txt
index f728567..029a1e9 100644
--- a/wear/compose/compose-material/api/restricted_current.txt
+++ b/wear/compose/compose-material/api/restricted_current.txt
@@ -4,8 +4,8 @@
   public final class AnimationKt {
   }
 
-  @androidx.compose.runtime.Immutable public final class AutoCenteringParams {
-    ctor public AutoCenteringParams(optional int itemIndex, optional int itemOffset);
+  @Deprecated @androidx.compose.runtime.Immutable public final class AutoCenteringParams {
+    ctor @Deprecated public AutoCenteringParams(optional int itemIndex, optional int itemOffset);
   }
 
   @androidx.compose.runtime.Stable public interface ButtonBorder {
@@ -262,17 +262,19 @@
   }
 
   public final class PickerDefaults {
+    method public androidx.wear.compose.foundation.lazy.ScalingParams defaultScalingParams(optional float edgeScale, optional float edgeAlpha, optional float minElementHeight, optional float maxElementHeight, optional float minTransitionArea, optional float maxTransitionArea, optional androidx.compose.animation.core.Easing scaleInterpolator, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Constraints,java.lang.Integer> viewportVerticalOffsetResolver);
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.FlingBehavior flingBehavior(androidx.wear.compose.material.PickerState state, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decay);
     method public float getDefaultGradientRatio();
-    method public androidx.wear.compose.material.ScalingParams scalingParams(optional float edgeScale, optional float edgeAlpha, optional float minElementHeight, optional float maxElementHeight, optional float minTransitionArea, optional float maxTransitionArea, optional androidx.compose.animation.core.Easing scaleInterpolator, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Constraints,java.lang.Integer> viewportVerticalOffsetResolver);
+    method @Deprecated public androidx.wear.compose.material.ScalingParams scalingParams(optional float edgeScale, optional float edgeAlpha, optional float minElementHeight, optional float maxElementHeight, optional float minTransitionArea, optional float maxTransitionArea, optional androidx.compose.animation.core.Easing scaleInterpolator, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Constraints,java.lang.Integer> viewportVerticalOffsetResolver);
     property public final float DefaultGradientRatio;
     field public static final androidx.wear.compose.material.PickerDefaults INSTANCE;
   }
 
   public final class PickerKt {
-    method @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material.PickerState state, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional androidx.wear.compose.material.ScalingParams scalingParams, optional float separation, optional float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,kotlin.Unit> option);
+    method @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material.PickerState state, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSelected, optional androidx.wear.compose.foundation.lazy.ScalingParams scalingParams, optional float separation, optional float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,kotlin.Unit> option);
     method @Deprecated @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material.PickerState state, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit>? readOnlyLabel, optional androidx.wear.compose.material.ScalingParams scalingParams, optional float separation, optional float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,kotlin.Unit> option);
-    method @Deprecated @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material.PickerState state, String contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,? extends kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<? extends kotlin.Unit> onSelected, optional androidx.wear.compose.material.ScalingParams scalingParams, optional float separation, optional float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,? extends kotlin.Unit> option);
+    method @Deprecated @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material.PickerState state, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,? extends kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<? extends kotlin.Unit> onSelected, optional androidx.wear.compose.material.ScalingParams scalingParams, optional float separation, optional float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,? extends kotlin.Unit> option);
+    method @Deprecated @androidx.compose.runtime.Composable public static void Picker(androidx.wear.compose.material.PickerState state, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional boolean readOnly, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,? extends kotlin.Unit>? readOnlyLabel, optional kotlin.jvm.functions.Function0<? extends kotlin.Unit> onSelected, optional androidx.wear.compose.material.ScalingParams scalingParams, optional float separation, optional float gradientRatio, optional long gradientColor, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.PickerScope,? super java.lang.Integer,? extends kotlin.Unit> option);
     method @androidx.compose.runtime.Composable public static androidx.wear.compose.material.PickerState rememberPickerState(int initialNumberOfOptions, optional int initiallySelectedOption, optional boolean repeatItems);
   }
 
@@ -325,7 +327,8 @@
 
   public final class PositionIndicatorKt {
     method @androidx.compose.runtime.Composable public static void PositionIndicator(androidx.compose.foundation.ScrollState scrollState, optional androidx.compose.ui.Modifier modifier, optional boolean reverseDirection);
-    method @androidx.compose.runtime.Composable public static void PositionIndicator(androidx.wear.compose.material.ScalingLazyListState scalingLazyListState, optional androidx.compose.ui.Modifier modifier, optional boolean reverseDirection);
+    method @androidx.compose.runtime.Composable public static void PositionIndicator(androidx.wear.compose.foundation.lazy.ScalingLazyListState scalingLazyListState, optional androidx.compose.ui.Modifier modifier, optional boolean reverseDirection);
+    method @Deprecated @androidx.compose.runtime.Composable public static void PositionIndicator(androidx.wear.compose.material.ScalingLazyListState scalingLazyListState, optional androidx.compose.ui.Modifier modifier, optional boolean reverseDirection);
     method @androidx.compose.runtime.Composable public static void PositionIndicator(androidx.compose.foundation.lazy.LazyListState lazyListState, optional androidx.compose.ui.Modifier modifier, optional boolean reverseDirection);
     method @androidx.compose.runtime.Composable public static void PositionIndicator(kotlin.jvm.functions.Function0<java.lang.Float> value, optional androidx.compose.ui.Modifier modifier, optional kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> range, optional long color, optional boolean reverseDirection, optional int position);
     method @androidx.compose.runtime.Composable public static void PositionIndicator(androidx.wear.compose.material.PositionIndicatorState state, float indicatorHeight, float indicatorWidth, float paddingHorizontal, optional androidx.compose.ui.Modifier modifier, optional long background, optional long color, optional boolean reverseDirection, optional int position);
@@ -384,43 +387,43 @@
     method @androidx.compose.runtime.Composable public static void Scaffold(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? vignette, optional kotlin.jvm.functions.Function0<kotlin.Unit>? positionIndicator, optional kotlin.jvm.functions.Function0<kotlin.Unit>? pageIndicator, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
-  public final class ScalingLazyColumnDefaults {
-    method public androidx.wear.compose.material.ScalingParams scalingParams(optional float edgeScale, optional float edgeAlpha, optional float minElementHeight, optional float maxElementHeight, optional float minTransitionArea, optional float maxTransitionArea, optional androidx.compose.animation.core.Easing scaleInterpolator, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Constraints,java.lang.Integer> viewportVerticalOffsetResolver);
-    method @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.FlingBehavior snapFlingBehavior(androidx.wear.compose.material.ScalingLazyListState state, optional float snapOffset, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decay);
-    field public static final androidx.wear.compose.material.ScalingLazyColumnDefaults INSTANCE;
+  @Deprecated public final class ScalingLazyColumnDefaults {
+    method @Deprecated public androidx.wear.compose.material.ScalingParams scalingParams(optional float edgeScale, optional float edgeAlpha, optional float minElementHeight, optional float maxElementHeight, optional float minTransitionArea, optional float maxTransitionArea, optional androidx.compose.animation.core.Easing scaleInterpolator, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Constraints,java.lang.Integer> viewportVerticalOffsetResolver);
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.foundation.gestures.FlingBehavior snapFlingBehavior(androidx.wear.compose.material.ScalingLazyListState state, optional float snapOffset, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decay);
+    field @Deprecated public static final androidx.wear.compose.material.ScalingLazyColumnDefaults INSTANCE;
   }
 
   public final class ScalingLazyColumnKt {
-    method @androidx.compose.runtime.Composable public static void ScalingLazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material.ScalingLazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.material.ScalingParams scalingParams, optional int anchorType, optional androidx.wear.compose.material.AutoCenteringParams? autoCentering, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material.ScalingLazyListScope,kotlin.Unit> content);
-    method public static inline <T> void items(androidx.wear.compose.material.ScalingLazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super T,kotlin.Unit> itemContent);
-    method public static inline <T> void items(androidx.wear.compose.material.ScalingLazyListScope, T![] items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super T,kotlin.Unit> itemContent);
-    method public static inline <T> void itemsIndexed(androidx.wear.compose.material.ScalingLazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
-    method public static inline <T> void itemsIndexed(androidx.wear.compose.material.ScalingLazyListScope, T![] items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method @Deprecated @androidx.compose.runtime.Composable public static void ScalingLazyColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material.ScalingLazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, optional androidx.wear.compose.material.ScalingParams scalingParams, optional int anchorType, optional androidx.wear.compose.material.AutoCenteringParams? autoCentering, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material.ScalingLazyListScope,kotlin.Unit> content);
+    method @Deprecated public static inline <T> void items(androidx.wear.compose.material.ScalingLazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super T,kotlin.Unit> itemContent);
+    method @Deprecated public static inline <T> void items(androidx.wear.compose.material.ScalingLazyListScope, T![] items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super T,kotlin.Unit> itemContent);
+    method @Deprecated public static inline <T> void itemsIndexed(androidx.wear.compose.material.ScalingLazyListScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method @Deprecated public static inline <T> void itemsIndexed(androidx.wear.compose.material.ScalingLazyListScope, T![] items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
   }
 
   public final class ScalingLazyColumnMeasureKt {
   }
 
-  @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class ScalingLazyListAnchorType {
-    field public static final androidx.wear.compose.material.ScalingLazyListAnchorType.Companion Companion;
+  @Deprecated @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class ScalingLazyListAnchorType {
+    field @Deprecated public static final androidx.wear.compose.material.ScalingLazyListAnchorType.Companion Companion;
   }
 
-  public static final class ScalingLazyListAnchorType.Companion {
-    method public int getItemCenter();
-    method public int getItemStart();
+  @Deprecated public static final class ScalingLazyListAnchorType.Companion {
+    method @Deprecated public int getItemCenter();
+    method @Deprecated public int getItemStart();
     property public final int ItemCenter;
     property public final int ItemStart;
   }
 
-  public sealed interface ScalingLazyListItemInfo {
-    method public float getAlpha();
-    method public int getIndex();
-    method public Object getKey();
-    method public int getOffset();
-    method public float getScale();
-    method public int getSize();
-    method public int getUnadjustedOffset();
-    method public int getUnadjustedSize();
+  @Deprecated public sealed interface ScalingLazyListItemInfo {
+    method @Deprecated public float getAlpha();
+    method @Deprecated public int getIndex();
+    method @Deprecated public Object getKey();
+    method @Deprecated public int getOffset();
+    method @Deprecated public float getScale();
+    method @Deprecated public int getSize();
+    method @Deprecated public int getUnadjustedOffset();
+    method @Deprecated public int getUnadjustedSize();
     property public abstract float alpha;
     property public abstract int index;
     property public abstract Object key;
@@ -431,24 +434,24 @@
     property public abstract int unadjustedSize;
   }
 
-  @androidx.compose.runtime.Stable @androidx.wear.compose.material.ScalingLazyScopeMarker public sealed interface ScalingLazyListItemScope {
-    method public androidx.compose.ui.Modifier fillParentMaxHeight(androidx.compose.ui.Modifier, optional float fraction);
-    method public androidx.compose.ui.Modifier fillParentMaxSize(androidx.compose.ui.Modifier, optional float fraction);
-    method public androidx.compose.ui.Modifier fillParentMaxWidth(androidx.compose.ui.Modifier, optional float fraction);
+  @Deprecated @androidx.compose.runtime.Stable @androidx.wear.compose.material.ScalingLazyScopeMarker public sealed interface ScalingLazyListItemScope {
+    method @Deprecated public androidx.compose.ui.Modifier fillParentMaxHeight(androidx.compose.ui.Modifier, optional float fraction);
+    method @Deprecated public androidx.compose.ui.Modifier fillParentMaxSize(androidx.compose.ui.Modifier, optional float fraction);
+    method @Deprecated public androidx.compose.ui.Modifier fillParentMaxWidth(androidx.compose.ui.Modifier, optional float fraction);
   }
 
-  public sealed interface ScalingLazyListLayoutInfo {
-    method public int getAfterAutoCenteringPadding();
-    method public int getAfterContentPadding();
-    method public int getBeforeAutoCenteringPadding();
-    method public int getBeforeContentPadding();
-    method public androidx.compose.foundation.gestures.Orientation getOrientation();
-    method public boolean getReverseLayout();
-    method public int getTotalItemsCount();
-    method public int getViewportEndOffset();
-    method public long getViewportSize();
-    method public int getViewportStartOffset();
-    method public java.util.List<androidx.wear.compose.material.ScalingLazyListItemInfo> getVisibleItemsInfo();
+  @Deprecated public sealed interface ScalingLazyListLayoutInfo {
+    method @Deprecated public int getAfterAutoCenteringPadding();
+    method @Deprecated public int getAfterContentPadding();
+    method @Deprecated public int getBeforeAutoCenteringPadding();
+    method @Deprecated public int getBeforeContentPadding();
+    method @Deprecated public androidx.compose.foundation.gestures.Orientation getOrientation();
+    method @Deprecated public boolean getReverseLayout();
+    method @Deprecated public int getTotalItemsCount();
+    method @Deprecated public int getViewportEndOffset();
+    method @Deprecated public long getViewportSize();
+    method @Deprecated public int getViewportStartOffset();
+    method @Deprecated public java.util.List<androidx.wear.compose.material.ScalingLazyListItemInfo> getVisibleItemsInfo();
     property public abstract int afterAutoCenteringPadding;
     property public abstract int afterContentPadding;
     property public abstract int beforeAutoCenteringPadding;
@@ -462,51 +465,51 @@
     property public abstract java.util.List<androidx.wear.compose.material.ScalingLazyListItemInfo> visibleItemsInfo;
   }
 
-  @androidx.wear.compose.material.ScalingLazyScopeMarker public sealed interface ScalingLazyListScope {
-    method public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material.ScalingLazyListItemScope,kotlin.Unit> content);
-    method public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
+  @Deprecated @androidx.wear.compose.material.ScalingLazyScopeMarker public sealed interface ScalingLazyListScope {
+    method @Deprecated public void item(optional Object? key, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material.ScalingLazyListItemScope,kotlin.Unit> content);
+    method @Deprecated public void items(int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.wear.compose.material.ScalingLazyListItemScope,? super java.lang.Integer,kotlin.Unit> itemContent);
   }
 
-  @androidx.compose.runtime.Stable public final class ScalingLazyListState implements androidx.compose.foundation.gestures.ScrollableState {
-    ctor public ScalingLazyListState(optional int initialCenterItemIndex, optional int initialCenterItemScrollOffset);
-    method public suspend Object? animateScrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public float dispatchRawDelta(float delta);
-    method public int getCenterItemIndex();
-    method public int getCenterItemScrollOffset();
-    method public androidx.wear.compose.material.ScalingLazyListLayoutInfo getLayoutInfo();
-    method public boolean isScrollInProgress();
-    method public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+  @Deprecated @androidx.compose.runtime.Stable public final class ScalingLazyListState implements androidx.compose.foundation.gestures.ScrollableState {
+    ctor @Deprecated public ScalingLazyListState(optional int initialCenterItemIndex, optional int initialCenterItemScrollOffset);
+    method @Deprecated public suspend Object? animateScrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @Deprecated public float dispatchRawDelta(float delta);
+    method @Deprecated public int getCenterItemIndex();
+    method @Deprecated public int getCenterItemScrollOffset();
+    method @Deprecated public androidx.wear.compose.material.ScalingLazyListLayoutInfo getLayoutInfo();
+    method @Deprecated public boolean isScrollInProgress();
+    method @Deprecated public suspend Object? scroll(androidx.compose.foundation.MutatePriority scrollPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.ScrollScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @Deprecated public suspend Object? scrollToItem(int index, optional int scrollOffset, optional kotlin.coroutines.Continuation<? super kotlin.Unit>);
     property public boolean canScrollBackward;
     property public boolean canScrollForward;
     property public final int centerItemIndex;
     property public final int centerItemScrollOffset;
     property public boolean isScrollInProgress;
     property public final androidx.wear.compose.material.ScalingLazyListLayoutInfo layoutInfo;
-    field public static final androidx.wear.compose.material.ScalingLazyListState.Companion Companion;
+    field @Deprecated public static final androidx.wear.compose.material.ScalingLazyListState.Companion Companion;
   }
 
-  public static final class ScalingLazyListState.Companion {
-    method public androidx.compose.runtime.saveable.Saver<androidx.wear.compose.material.ScalingLazyListState,java.lang.Object> getSaver();
+  @Deprecated public static final class ScalingLazyListState.Companion {
+    method @Deprecated public androidx.compose.runtime.saveable.Saver<androidx.wear.compose.material.ScalingLazyListState,java.lang.Object> getSaver();
     property public final androidx.compose.runtime.saveable.Saver<androidx.wear.compose.material.ScalingLazyListState,java.lang.Object> Saver;
   }
 
   public final class ScalingLazyListStateKt {
-    method @androidx.compose.runtime.Composable public static androidx.wear.compose.material.ScalingLazyListState rememberScalingLazyListState(optional int initialCenterItemIndex, optional int initialCenterItemScrollOffset);
+    method @Deprecated @androidx.compose.runtime.Composable public static androidx.wear.compose.material.ScalingLazyListState rememberScalingLazyListState(optional int initialCenterItemIndex, optional int initialCenterItemScrollOffset);
   }
 
-  @kotlin.DslMarker public @interface ScalingLazyScopeMarker {
+  @Deprecated @kotlin.DslMarker public @interface ScalingLazyScopeMarker {
   }
 
-  @androidx.compose.runtime.Stable public interface ScalingParams {
-    method public float getEdgeAlpha();
-    method public float getEdgeScale();
-    method public float getMaxElementHeight();
-    method public float getMaxTransitionArea();
-    method public float getMinElementHeight();
-    method public float getMinTransitionArea();
-    method public androidx.compose.animation.core.Easing getScaleInterpolator();
-    method public int resolveViewportVerticalOffset(long viewportConstraints);
+  @Deprecated @androidx.compose.runtime.Stable public interface ScalingParams {
+    method @Deprecated public float getEdgeAlpha();
+    method @Deprecated public float getEdgeScale();
+    method @Deprecated public float getMaxElementHeight();
+    method @Deprecated public float getMaxTransitionArea();
+    method @Deprecated public float getMinElementHeight();
+    method @Deprecated public float getMinTransitionArea();
+    method @Deprecated public androidx.compose.animation.core.Easing getScaleInterpolator();
+    method @Deprecated public int resolveViewportVerticalOffset(long viewportConstraints);
     property public abstract float edgeAlpha;
     property public abstract float edgeScale;
     property public abstract float maxElementHeight;
@@ -519,7 +522,8 @@
   public final class ScrollAwayKt {
     method public static androidx.compose.ui.Modifier scrollAway(androidx.compose.ui.Modifier, androidx.compose.foundation.ScrollState scrollState, optional float offset);
     method public static androidx.compose.ui.Modifier scrollAway(androidx.compose.ui.Modifier, androidx.compose.foundation.lazy.LazyListState scrollState, optional int itemIndex, optional float offset);
-    method public static androidx.compose.ui.Modifier scrollAway(androidx.compose.ui.Modifier, androidx.wear.compose.material.ScalingLazyListState scrollState, optional int itemIndex, optional float offset);
+    method public static androidx.compose.ui.Modifier scrollAway(androidx.compose.ui.Modifier, androidx.wear.compose.foundation.lazy.ScalingLazyListState scrollState, optional int itemIndex, optional float offset);
+    method @Deprecated public static androidx.compose.ui.Modifier scrollAway(androidx.compose.ui.Modifier, androidx.wear.compose.material.ScalingLazyListState scrollState, optional int itemIndex, optional float offset);
   }
 
   @androidx.compose.runtime.Immutable public final class Shapes {
@@ -783,13 +787,17 @@
   }
 
   public final class DialogKt {
-    method @androidx.compose.runtime.Composable public static void Alert(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> negativeButton, kotlin.jvm.functions.Function0<kotlin.Unit> positiveButton, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? icon, optional androidx.wear.compose.material.ScalingLazyListState scrollState, optional long backgroundColor, optional long contentColor, optional long titleColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? content);
-    method @androidx.compose.runtime.Composable public static void Alert(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? icon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? message, optional androidx.wear.compose.material.ScalingLazyListState scrollState, optional long backgroundColor, optional long titleColor, optional long messageColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material.ScalingLazyListScope,kotlin.Unit> content);
-    method @androidx.compose.runtime.Composable public static void Confirmation(kotlin.jvm.functions.Function0<kotlin.Unit> onTimeout, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? icon, optional androidx.wear.compose.material.ScalingLazyListState scrollState, optional long durationMillis, optional long backgroundColor, optional long contentColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void Alert(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> title, kotlin.jvm.functions.Function0<kotlin.Unit> negativeButton, kotlin.jvm.functions.Function0<kotlin.Unit> positiveButton, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? icon, optional androidx.wear.compose.foundation.lazy.ScalingLazyListState scrollState, optional long backgroundColor, optional long contentColor, optional long titleColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? content);
+    method @androidx.compose.runtime.Composable public static void Alert(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? icon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? message, optional androidx.wear.compose.foundation.lazy.ScalingLazyListState scrollState, optional long backgroundColor, optional long titleColor, optional long messageColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.wear.compose.foundation.lazy.ScalingLazyListScope,kotlin.Unit> content);
+    method @Deprecated @androidx.compose.runtime.Composable public static void Alert(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit> title, kotlin.jvm.functions.Function0<? extends kotlin.Unit> negativeButton, kotlin.jvm.functions.Function0<? extends kotlin.Unit> positiveButton, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit>? icon, optional androidx.wear.compose.material.ScalingLazyListState scrollState, optional long backgroundColor, optional long contentColor, optional long titleColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit>? content);
+    method @Deprecated @androidx.compose.runtime.Composable public static void Alert(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit>? icon, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit>? message, optional androidx.wear.compose.material.ScalingLazyListState scrollState, optional long backgroundColor, optional long titleColor, optional long messageColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.wear.compose.material.ScalingLazyListScope,? extends kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void Confirmation(kotlin.jvm.functions.Function0<kotlin.Unit> onTimeout, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit>? icon, optional androidx.wear.compose.foundation.lazy.ScalingLazyListState scrollState, optional long durationMillis, optional long backgroundColor, optional long contentColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @Deprecated @androidx.compose.runtime.Composable public static void Confirmation(kotlin.jvm.functions.Function0<? extends kotlin.Unit> onTimeout, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit>? icon, optional androidx.wear.compose.material.ScalingLazyListState scrollState, optional long durationMillis, optional long backgroundColor, optional long contentColor, optional long iconColor, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,? extends kotlin.Unit> content);
   }
 
   public final class Dialog_androidKt {
-    method @androidx.compose.runtime.Composable public static void Dialog(boolean showDialog, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material.ScalingLazyListState? scrollState, optional androidx.compose.ui.window.DialogProperties properties, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static void Dialog(boolean showDialog, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.foundation.lazy.ScalingLazyListState? scrollState, optional androidx.compose.ui.window.DialogProperties properties, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @Deprecated @androidx.compose.runtime.Composable public static void Dialog(boolean showDialog, kotlin.jvm.functions.Function0<? extends kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material.ScalingLazyListState? scrollState, optional androidx.compose.ui.window.DialogProperties properties, kotlin.jvm.functions.Function0<? extends kotlin.Unit> content);
   }
 
 }
diff --git a/wear/compose/compose-material/benchmark/src/androidTest/java/androidx/wear/compose/material/benchmark/ScalingLazyColumnBenchmark.kt b/wear/compose/compose-material/benchmark/src/androidTest/java/androidx/wear/compose/material/benchmark/ScalingLazyColumnBenchmark.kt
index dbb84ac..b352e1c 100644
--- a/wear/compose/compose-material/benchmark/src/androidTest/java/androidx/wear/compose/material/benchmark/ScalingLazyColumnBenchmark.kt
+++ b/wear/compose/compose-material/benchmark/src/androidTest/java/androidx/wear/compose/material/benchmark/ScalingLazyColumnBenchmark.kt
@@ -88,6 +88,7 @@
     }
 }
 
+@Suppress("DEPRECATION")
 internal class ScalingLazyColumnTestCase : LayeredComposeTestCase() {
     private var itemSizeDp: Dp = 10.dp
     private var defaultItemSpacingDp: Dp = 4.dp
diff --git a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/DialogSample.kt b/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/DialogSample.kt
index 7d233f6..e402617 100644
--- a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/DialogSample.kt
+++ b/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/DialogSample.kt
@@ -41,6 +41,7 @@
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
 import androidx.wear.compose.material.Button
 import androidx.wear.compose.material.ButtonDefaults
 import androidx.wear.compose.material.Chip
@@ -51,7 +52,6 @@
 import androidx.wear.compose.material.dialog.Alert
 import androidx.wear.compose.material.dialog.Confirmation
 import androidx.wear.compose.material.dialog.Dialog
-import androidx.wear.compose.material.rememberScalingLazyListState
 
 @Sampled
 @Composable
diff --git a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/ScaffoldSample.kt b/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/ScaffoldSample.kt
index 31f3bd0..9aa2006 100644
--- a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/ScaffoldSample.kt
+++ b/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/ScaffoldSample.kt
@@ -28,12 +28,12 @@
 import androidx.wear.compose.material.ChipDefaults
 import androidx.wear.compose.material.PositionIndicator
 import androidx.wear.compose.material.Scaffold
-import androidx.wear.compose.material.ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
 import androidx.wear.compose.material.Text
 import androidx.wear.compose.material.TimeText
 import androidx.wear.compose.material.Vignette
 import androidx.wear.compose.material.VignettePosition
-import androidx.wear.compose.material.rememberScalingLazyListState
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
 
 @SuppressLint("UnrememberedMutableState")
 @Sampled
diff --git a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/ScalingLazyColumnSample.kt b/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/ScalingLazyColumnSample.kt
index c877c44..1826758 100644
--- a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/ScalingLazyColumnSample.kt
+++ b/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/ScalingLazyColumnSample.kt
@@ -25,15 +25,15 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.dp
-import androidx.wear.compose.material.AutoCenteringParams
+import androidx.wear.compose.foundation.lazy.AutoCenteringParams
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumnDefaults
+import androidx.wear.compose.foundation.lazy.ScalingLazyListAnchorType
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
 import androidx.wear.compose.material.Chip
 import androidx.wear.compose.material.ChipDefaults
 import androidx.wear.compose.material.ListHeader
-import androidx.wear.compose.material.ScalingLazyColumn
-import androidx.wear.compose.material.ScalingLazyColumnDefaults
-import androidx.wear.compose.material.ScalingLazyListAnchorType
 import androidx.wear.compose.material.Text
-import androidx.wear.compose.material.rememberScalingLazyListState
 import kotlinx.coroutines.launch
 
 @Sampled
diff --git a/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/PickerTest.kt b/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/PickerTest.kt
index ac201f8..7829b10 100644
--- a/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/PickerTest.kt
+++ b/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/PickerTest.kt
@@ -43,7 +43,6 @@
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
@@ -180,23 +179,44 @@
         assertThat(state.selectedOption).isEqualTo(numberOfOptions - 1)
     }
 
-    @SdkSuppress(minSdkVersion = 29) // b/260234023
     @Test
     fun uses_positive_separation_correctly() =
-        uses_separation_correctly(1)
+        scroll_with_separation(1)
 
     @Test
     fun uses_negative_separation_correctly() =
-        uses_separation_correctly(-1)
+        scroll_with_separation(-1)
 
-    private fun uses_separation_correctly(separationSign: Int) {
+    /**
+     * Test that picker is properly scrolled with scrollOffset, which equals to a half of the
+     * (itemSizePx + separationPx) and minus 1 pixel
+     * for making it definitely less than a half of an item
+     */
+    @Test
+    fun scroll_with_positive_separation_and_offset() =
+        scroll_with_separation(1, scrollOffset = (itemSizePx + separationPx) / 2 - 1)
+
+    /**
+     * Test that picker is properly scrolled with scrollOffset, which equals to a half of the
+     * (itemSizePx - separationPx) and minus 1 pixel
+     * for making it definitely less than a half of an item
+     */
+    @Test
+    fun scroll_with_negative_separation_and_offset() =
+        scroll_with_separation(-1, scrollOffset = (itemSizePx - separationPx) / 2 - 1)
+
+    private fun scroll_with_separation(
+        separationSign: Int,
+        scrollOffset: Int = 0
+    ) {
         lateinit var state: PickerState
         rule.setContent {
             WithTouchSlop(0f) {
                 Picker(
                     state = rememberPickerState(20).also { state = it },
                     contentDescription = CONTENT_DESCRIPTION,
-                    modifier = Modifier.testTag(TEST_TAG)
+                    modifier = Modifier
+                        .testTag(TEST_TAG)
                         .requiredSize(itemSizeDp * 11 + separationDp * 10 * separationSign),
                     separation = separationDp * separationSign
                 ) {
@@ -209,12 +229,14 @@
         val itemsToScroll = 4
 
         rule.onNodeWithTag(TEST_TAG).performTouchInput {
-            // Start at bottom - 2 to allow for 1.dp padding around the Picker
+            // Start at bottom - 5 to allow for around 2.dp padding around the Picker
             // (which was added to prevent jitter around the start of the gradient).
             swipeWithVelocity(
-                start = Offset(centerX, bottom - 2),
-                end = Offset(centerX, bottom - 2 -
-                    (itemSizePx + separationPx * separationSign) * itemsToScroll),
+                start = Offset(centerX, bottom - 5),
+                end = Offset(
+                    centerX, bottom - 5 - scrollOffset -
+                        (itemSizePx + separationPx * separationSign) * itemsToScroll
+                ),
                 endVelocity = NOT_A_FLING_SPEED
             )
         }
diff --git a/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/PositionIndicatorTest.kt b/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/PositionIndicatorTest.kt
index bb27e59..246fd6a 100644
--- a/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/PositionIndicatorTest.kt
+++ b/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/PositionIndicatorTest.kt
@@ -16,6 +16,13 @@
 
 package androidx.wear.compose.material
 
+import androidx.wear.compose.foundation.lazy.ScalingLazyListLayoutInfo as ScalingLazyListLayoutInfo
+import androidx.wear.compose.foundation.lazy.ScalingLazyListState as ScalingLazyListState
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState as rememberScalingLazyListState
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn as ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumnDefaults as ScalingLazyColumnDefaults
+import androidx.wear.compose.foundation.lazy.AutoCenteringParams as AutoCenteringParams
+import androidx.wear.compose.foundation.lazy.ScalingLazyListScope as ScalingLazyListScope
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.ScrollState
 import androidx.compose.foundation.background
@@ -24,16 +31,19 @@
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyListLayoutInfo
+import androidx.compose.foundation.lazy.LazyListScope
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -73,38 +83,9 @@
 
     @Test
     fun emptyScalingLazyColumnGivesCorrectPositionAndSize() {
-        lateinit var state: ScalingLazyListState
-        lateinit var positionIndicatorState: PositionIndicatorState
-        var viewPortHeight = 0
-        rule.setContent {
-            state = rememberScalingLazyListState()
-            positionIndicatorState = ScalingLazyColumnStateAdapter(state)
-            ScalingLazyColumn(
-                state = state,
-                verticalArrangement = Arrangement.spacedBy(itemSpacingDp),
-                modifier = Modifier
-                    .onSizeChanged { viewPortHeight = it.height }
-                    .requiredSize(itemSizeDp * 3.5f + itemSpacingDp * 2.5f)
-            ) {
-            }
-            PositionIndicator(
-                state = positionIndicatorState,
-                indicatorHeight = 50.dp,
-                indicatorWidth = 4.dp,
-                paddingHorizontal = 5.dp,
-            )
-        }
-
-        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
-        rule.waitUntil { state.initialized.value }
-        rule.runOnIdle {
-            assertThat(
-                positionIndicatorState.positionFraction
-            ).isEqualTo(0)
-            assertThat(
-                positionIndicatorState.sizeFraction(viewPortHeight.toFloat())
-            ).isEqualTo(1)
-        }
+        scalingLazyColumnNotLargeEnoughToScroll(
+            Arrangement.spacedBy(itemSpacingDp)
+        ) {}
     }
 
     @Test
@@ -118,46 +99,36 @@
     }
 
     private fun scalingLazyColumnNotLargeEnoughToScrollGivesCorrectPositionAndSize(itemSize: Dp) {
-        lateinit var state: ScalingLazyListState
-        lateinit var positionIndicatorState: PositionIndicatorState
-        var viewPortHeight = 0
-        rule.setContent {
-            state = rememberScalingLazyListState()
-            positionIndicatorState = ScalingLazyColumnStateAdapter(state)
-            ScalingLazyColumn(
-                state = state,
-                verticalArrangement = Arrangement.spacedBy(itemSpacingDp),
-                modifier = Modifier
-                    .onSizeChanged { viewPortHeight = it.height }
-                    .requiredSize(itemSizeDp * 3.5f + itemSpacingDp * 2.5f),
-                autoCentering = null
-            ) {
-                items(3) {
-                    Box(Modifier.requiredSize(itemSize))
-                }
+        scalingLazyColumnNotLargeEnoughToScroll(
+            Arrangement.spacedBy(itemSpacingDp),
+            autoCentering = null
+        ) {
+            items(3) {
+                Box(Modifier.requiredSize(itemSize))
             }
-            PositionIndicator(
-                state = positionIndicatorState,
-                indicatorHeight = 50.dp,
-                indicatorWidth = 4.dp,
-                paddingHorizontal = 5.dp,
-            )
-        }
-
-        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
-        rule.waitUntil { state.initialized.value }
-        rule.runOnIdle {
-            assertThat(
-                positionIndicatorState.positionFraction
-            ).isEqualTo(0f)
-            assertThat(
-                positionIndicatorState.sizeFraction(viewPortHeight.toFloat())
-            ).isEqualTo(1f)
         }
     }
 
     @Test
     fun scalingLazyColumnNotLargeEnoughToScrollSwapVerticalAlignmentGivesCorrectPositionAndSize() {
+        scalingLazyColumnNotLargeEnoughToScroll(
+            Arrangement.spacedBy(
+                space = itemSpacingDp,
+                alignment = Alignment.Bottom
+            )
+        ) {
+            items(3) {
+                Box(Modifier.requiredSize(itemSizeDp))
+            }
+        }
+    }
+
+    private fun scalingLazyColumnNotLargeEnoughToScroll(
+        verticalArrangement: Arrangement.Vertical,
+        reverseLayout: Boolean = false,
+        autoCentering: AutoCenteringParams? = AutoCenteringParams(),
+        content: ScalingLazyListScope.() -> Unit
+    ) {
         lateinit var state: ScalingLazyListState
         lateinit var positionIndicatorState: PositionIndicatorState
         var viewPortHeight = 0
@@ -166,17 +137,14 @@
             positionIndicatorState = ScalingLazyColumnStateAdapter(state)
             ScalingLazyColumn(
                 state = state,
-                verticalArrangement = Arrangement.spacedBy(
-                    space = itemSpacingDp,
-                    alignment = Alignment.Bottom
-                ),
+                verticalArrangement = verticalArrangement,
+                reverseLayout = reverseLayout,
                 modifier = Modifier
                     .onSizeChanged { viewPortHeight = it.height }
-                    .requiredSize(itemSizeDp * 3.5f + itemSpacingDp * 2.5f)
+                    .requiredSize(itemSizeDp * 3.5f + itemSpacingDp * 2.5f),
+                autoCentering = autoCentering
             ) {
-                items(3) {
-                    Box(Modifier.requiredSize(itemSizeDp))
-                }
+                content(this)
             }
             PositionIndicator(
                 state = positionIndicatorState,
@@ -186,8 +154,6 @@
             )
         }
 
-        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
-        rule.waitUntil { state.initialized.value }
         rule.runOnIdle {
             assertThat(
                 positionIndicatorState.positionFraction
@@ -250,7 +216,11 @@
                 }
             ) {
                 items(5) {
-                    Box(Modifier.requiredSize(itemSizeDp).border(BorderStroke(1.dp, Color.Green)))
+                    Box(
+                        Modifier
+                            .requiredSize(itemSizeDp)
+                            .border(BorderStroke(1.dp, Color.Green))
+                    )
                 }
             }
             PositionIndicator(
@@ -261,8 +231,6 @@
             )
         }
 
-        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
-        rule.waitUntil { state.initialized.value }
         rule.runOnIdle {
             // Scroll forwards so that item with index 2 is in the center of the viewport
             runBlocking {
@@ -286,84 +254,25 @@
 
     @Test
     fun emptyReverseLayoutScalingLazyColumnGivesCorrectPositionAndSize() {
-        lateinit var state: ScalingLazyListState
-        lateinit var positionIndicatorState: PositionIndicatorState
-        var viewPortHeight = 0
-        rule.setContent {
-            state = rememberScalingLazyListState()
-            positionIndicatorState = ScalingLazyColumnStateAdapter(state)
-            ScalingLazyColumn(
-                state = state,
-                verticalArrangement = Arrangement.spacedBy(itemSpacingDp),
-                reverseLayout = true,
-                modifier = Modifier
-                    .onSizeChanged { viewPortHeight = it.height }
-                    .requiredSize(itemSizeDp * 3.5f + itemSpacingDp * 2.5f)
-            ) {
-            }
-            PositionIndicator(
-                state = positionIndicatorState,
-                indicatorHeight = 50.dp,
-                indicatorWidth = 4.dp,
-                paddingHorizontal = 5.dp,
-            )
-        }
-
-        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
-        rule.waitUntil { state.initialized.value }
-        rule.runOnIdle {
-            assertThat(
-                positionIndicatorState.positionFraction
-            ).isEqualTo(0)
-            assertThat(
-                positionIndicatorState.sizeFraction(viewPortHeight.toFloat())
-            ).isEqualTo(1)
-        }
+        scalingLazyColumnNotLargeEnoughToScroll(
+            Arrangement.spacedBy(itemSpacingDp),
+            reverseLayout = true
+        ) {}
     }
 
     @Test
     fun reverseLayoutScalingLazyColumnNotLargeEnoughToScrollGivesCorrectPositionAndSize() {
-        lateinit var state: ScalingLazyListState
-        lateinit var positionIndicatorState: PositionIndicatorState
-        var viewPortHeight = 0
-        rule.setContent {
-            state = rememberScalingLazyListState()
-            positionIndicatorState = ScalingLazyColumnStateAdapter(state)
-            ScalingLazyColumn(
-                state = state,
-                verticalArrangement = Arrangement.spacedBy(
-                    space = itemSpacingDp,
-                    alignment = Alignment.Bottom
-                ),
-                reverseLayout = true,
-                modifier = Modifier
-                    .onSizeChanged { viewPortHeight = it.height }
-                    .fillMaxWidth()
-                    .requiredSize(itemSizeDp * 3.5f + itemSpacingDp * 2.5f)
-                    .background(Color.DarkGray),
-                autoCentering = null
-            ) {
-                items(3) {
-                    Box(Modifier.requiredSize(itemSizeDp))
-                }
+        scalingLazyColumnNotLargeEnoughToScroll(
+            Arrangement.spacedBy(
+                space = itemSpacingDp,
+                alignment = Alignment.Bottom
+            ),
+            autoCentering = null,
+            reverseLayout = true
+        ) {
+            items(3) {
+                Box(Modifier.requiredSize(itemSizeDp))
             }
-            PositionIndicator(
-                state = positionIndicatorState,
-                indicatorHeight = 50.dp,
-                indicatorWidth = 4.dp,
-                paddingHorizontal = 5.dp,
-            )
-        }
-
-        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
-        rule.waitUntil { state.initialized.value }
-        rule.runOnIdle {
-            assertThat(
-                positionIndicatorState.positionFraction
-            ).isEqualTo(0f)
-            assertThat(
-                positionIndicatorState.sizeFraction(viewPortHeight.toFloat())
-            ).isEqualTo(1f)
         }
     }
 
@@ -401,8 +310,6 @@
             )
         }
 
-        // TODO(b/210654937): Remove the waitUntil once we no longer need 2 stage initialization
-        rule.waitUntil { state.initialized.value }
         rule.runOnIdle {
             runBlocking {
                 state.scrollBy(itemSizePx.toFloat() + itemSpacingPx.toFloat())
@@ -425,86 +332,52 @@
 
     @Test
     fun emptyLazyColumnGivesCorrectPositionAndSize() {
-        lateinit var state: LazyListState
-        lateinit var positionIndicatorState: PositionIndicatorState
-        var viewPortHeight = 0
-        rule.setContent {
-            state = rememberLazyListState()
-            positionIndicatorState = LazyColumnStateAdapter(state)
-            LazyColumn(
-                state = state,
-                verticalArrangement = Arrangement.spacedBy(itemSpacingDp),
-                modifier = Modifier
-                    .onSizeChanged { viewPortHeight = it.height }
-                    .requiredSize(itemSizeDp * 3.5f + itemSpacingDp * 2.5f)
-            ) {
-            }
-            PositionIndicator(
-                state = positionIndicatorState,
-                indicatorHeight = 50.dp,
-                indicatorWidth = 4.dp,
-                paddingHorizontal = 5.dp,
-            )
-        }
-
-        rule.runOnIdle {
-            assertThat(
-                positionIndicatorState.positionFraction
-            ).isEqualTo(0)
-            assertThat(
-                positionIndicatorState.sizeFraction(viewPortHeight.toFloat())
-            ).isEqualTo(1)
-        }
+        lazyColumnNotLargeEnoughToScroll(
+            Arrangement.spacedBy(itemSpacingDp)
+        ) {}
     }
 
     @Test
     fun lazyColumnNotLargeEnoughToScrollGivesCorrectPositionAndSize() {
-        lazyColumnNotLargeEnoughToScrollGivesCorrectPositionAndSize(itemSizeDp)
+        lazyColumnNotLargeEnoughToScroll(
+            Arrangement.spacedBy(itemSpacingDp)
+        ) {
+            items(3) {
+                Box(Modifier.requiredSize(itemSizeDp))
+            }
+        }
     }
 
     @Test
     fun lazyColumnNotLargeEnoughToScrollGivesCorrectPositionAndSizeForZeroSizeItems() {
-        lazyColumnNotLargeEnoughToScrollGivesCorrectPositionAndSize(0.dp)
-    }
-
-    private fun lazyColumnNotLargeEnoughToScrollGivesCorrectPositionAndSize(itemSize: Dp) {
-        lateinit var state: LazyListState
-        lateinit var positionIndicatorState: PositionIndicatorState
-        var viewPortHeight = 0
-        rule.setContent {
-            state = rememberLazyListState()
-            positionIndicatorState = LazyColumnStateAdapter(state)
-            LazyColumn(
-                state = state,
-                verticalArrangement = Arrangement.spacedBy(itemSpacingDp),
-                modifier = Modifier
-                    .onSizeChanged { viewPortHeight = it.height }
-                    .requiredSize(itemSizeDp * 3.5f + itemSpacingDp * 2.5f)
-            ) {
-                items(3) {
-                    Box(Modifier.requiredSize(itemSize))
-                }
+        lazyColumnNotLargeEnoughToScroll(
+            Arrangement.spacedBy(itemSpacingDp)
+        ) {
+            items(3) {
+                Box(Modifier.requiredSize(0.dp))
             }
-            PositionIndicator(
-                state = positionIndicatorState,
-                indicatorHeight = 50.dp,
-                indicatorWidth = 4.dp,
-                paddingHorizontal = 5.dp,
-            )
-        }
-
-        rule.runOnIdle {
-            assertThat(
-                positionIndicatorState.positionFraction
-            ).isEqualTo(0f)
-            assertThat(
-                positionIndicatorState.sizeFraction(viewPortHeight.toFloat())
-            ).isEqualTo(1f)
         }
     }
 
     @Test
     fun lazyColumnNotLargeEnoughToScrollSwapVerticalAlignmentGivesCorrectPositionAndSize() {
+        lazyColumnNotLargeEnoughToScroll(
+            Arrangement.spacedBy(
+                space = itemSpacingDp,
+                alignment = Alignment.Bottom
+            )
+        ) {
+            items(3) {
+                Box(Modifier.requiredSize(itemSizeDp))
+            }
+        }
+    }
+
+    private fun lazyColumnNotLargeEnoughToScroll(
+        verticalArrangement: Arrangement.Vertical,
+        reverseLayout: Boolean = false,
+        content: LazyListScope.() -> Unit
+    ) {
         lateinit var state: LazyListState
         lateinit var positionIndicatorState: PositionIndicatorState
         var viewPortHeight = 0
@@ -513,17 +386,13 @@
             positionIndicatorState = LazyColumnStateAdapter(state)
             LazyColumn(
                 state = state,
-                verticalArrangement = Arrangement.spacedBy(
-                    space = itemSpacingDp,
-                    alignment = Alignment.Bottom
-                ),
+                verticalArrangement = verticalArrangement,
+                reverseLayout = reverseLayout,
                 modifier = Modifier
                     .onSizeChanged { viewPortHeight = it.height }
                     .requiredSize(itemSizeDp * 3.5f + itemSpacingDp * 2.5f)
             ) {
-                items(3) {
-                    Box(Modifier.requiredSize(itemSizeDp))
-                }
+                content()
             }
             PositionIndicator(
                 state = positionIndicatorState,
@@ -591,79 +460,24 @@
 
     @Test
     fun emptyReverseLayoutLazyColumnGivesCorrectPositionAndSize() {
-        lateinit var state: LazyListState
-        lateinit var positionIndicatorState: PositionIndicatorState
-        var viewPortHeight = 0
-        rule.setContent {
-            state = rememberLazyListState()
-            positionIndicatorState = LazyColumnStateAdapter(state)
-            LazyColumn(
-                state = state,
-                verticalArrangement = Arrangement.spacedBy(itemSpacingDp),
-                reverseLayout = true,
-                modifier = Modifier
-                    .onSizeChanged { viewPortHeight = it.height }
-                    .requiredSize(itemSizeDp * 3.5f + itemSpacingDp * 2.5f)
-            ) {
-            }
-            PositionIndicator(
-                state = positionIndicatorState,
-                indicatorHeight = 50.dp,
-                indicatorWidth = 4.dp,
-                paddingHorizontal = 5.dp,
-            )
-        }
-
-        rule.runOnIdle {
-            assertThat(
-                positionIndicatorState.positionFraction
-            ).isEqualTo(0)
-            assertThat(
-                positionIndicatorState.sizeFraction(viewPortHeight.toFloat())
-            ).isEqualTo(1)
-        }
+        lazyColumnNotLargeEnoughToScroll(
+            Arrangement.spacedBy(itemSpacingDp),
+            reverseLayout = true
+        ) {}
     }
 
     @Test
     fun reverseLayoutLazyColumnNotLargeEnoughToScrollGivesCorrectPositionAndSize() {
-        lateinit var state: LazyListState
-        lateinit var positionIndicatorState: PositionIndicatorState
-        var viewPortHeight = 0
-        rule.setContent {
-            state = rememberLazyListState()
-            positionIndicatorState = LazyColumnStateAdapter(state)
-            LazyColumn(
-                state = state,
-                verticalArrangement = Arrangement.spacedBy(
-                    space = itemSpacingDp,
-                    alignment = Alignment.Bottom
-                ),
-                reverseLayout = true,
-                modifier = Modifier
-                    .onSizeChanged { viewPortHeight = it.height }
-                    .fillMaxWidth()
-                    .requiredSize(itemSizeDp * 3.5f + itemSpacingDp * 2.5f)
-                    .background(Color.DarkGray),
-            ) {
-                items(3) {
-                    Box(Modifier.requiredSize(itemSizeDp))
-                }
+        lazyColumnNotLargeEnoughToScroll(
+            Arrangement.spacedBy(
+                space = itemSpacingDp,
+                alignment = Alignment.Bottom
+            ),
+            reverseLayout = true
+        ) {
+            items(3) {
+                Box(Modifier.requiredSize(itemSizeDp))
             }
-            PositionIndicator(
-                state = positionIndicatorState,
-                indicatorHeight = 50.dp,
-                indicatorWidth = 4.dp,
-                paddingHorizontal = 5.dp,
-            )
-        }
-
-        rule.runOnIdle {
-            assertThat(
-                positionIndicatorState.positionFraction
-            ).isEqualTo(0f)
-            assertThat(
-                positionIndicatorState.sizeFraction(viewPortHeight.toFloat())
-            ).isEqualTo(1f)
         }
     }
 
@@ -716,82 +530,56 @@
 
     @Test
     fun emptyScrollableColumnGivesCorrectPositionAndSize() {
-        lateinit var state: ScrollState
-        lateinit var positionIndicatorState: PositionIndicatorState
-        var viewPortHeight = 0
-        rule.setContent {
-            state = rememberScrollState()
-            positionIndicatorState = ScrollStateAdapter(scrollState = state)
-            Column(
-                modifier = Modifier
-                    .onSizeChanged { viewPortHeight = it.height }
-                    .requiredSize(itemSizeDp * 3.5f + itemSpacingDp * 2.5f)
-                    .verticalScroll(state = state)
-            ) {
-            }
-            PositionIndicator(
-                state = positionIndicatorState,
-                indicatorHeight = 50.dp,
-                indicatorWidth = 4.dp,
-                paddingHorizontal = 5.dp,
-            )
-        }
-
-        rule.runOnIdle {
-            assertThat(
-                positionIndicatorState.positionFraction
-            ).isEqualTo(0)
-            assertThat(
-                positionIndicatorState.sizeFraction(viewPortHeight.toFloat())
-            ).isEqualTo(1)
-        }
+        scrollableColumnNotLargeEnoughToScroll({})
     }
 
     @Test
     fun emptyReversedScrollableColumnGivesCorrectPositionAndSize() {
-        lateinit var state: ScrollState
-        lateinit var positionIndicatorState: PositionIndicatorState
-        var viewPortHeight = 0
-        rule.setContent {
-            state = rememberScrollState()
-            positionIndicatorState = ScrollStateAdapter(scrollState = state)
-            Column(
-                modifier = Modifier
-                    .onSizeChanged { viewPortHeight = it.height }
-                    .requiredSize(itemSizeDp * 3.5f + itemSpacingDp * 2.5f)
-                    .verticalScroll(state = state, reverseScrolling = true)
-            ) {
-            }
-            PositionIndicator(
-                state = positionIndicatorState,
-                indicatorHeight = 50.dp,
-                indicatorWidth = 4.dp,
-                paddingHorizontal = 5.dp,
-                reverseDirection = true,
-            )
-        }
-
-        rule.runOnIdle {
-            assertThat(
-                positionIndicatorState.positionFraction
-            ).isEqualTo(0)
-            assertThat(
-                positionIndicatorState.sizeFraction(viewPortHeight.toFloat())
-            ).isEqualTo(1)
-        }
+        scrollableColumnNotLargeEnoughToScroll({}, reverseScrolling = true)
     }
 
     @Test
     fun scrollableColumnNotLargeEnoughToScrollGivesCorrectPositionAndSize() {
-        scrollableColumnNotLargeEnoughToScrollGivesCorrectPositionAndSize(itemSizeDp)
+        scrollableColumnNotLargeEnoughToScroll(
+            {
+                Box(Modifier.requiredSize(itemSizeDp))
+                Box(Modifier.requiredSize(itemSizeDp))
+                Box(Modifier.requiredSize(itemSizeDp))
+            },
+            Arrangement.spacedBy(itemSpacingDp),
+        )
     }
 
     @Test
     fun scrollableColumnNotLargeEnoughToScrollGivesCorrectPositionAndSizeForZeroSizeItems() {
-        scrollableColumnNotLargeEnoughToScrollGivesCorrectPositionAndSize(0.dp)
+        scrollableColumnNotLargeEnoughToScroll(
+            {
+                Box(Modifier.requiredSize(0.dp))
+                Box(Modifier.requiredSize(0.dp))
+                Box(Modifier.requiredSize(0.dp))
+            },
+            Arrangement.spacedBy(itemSpacingDp),
+        )
     }
 
-    private fun scrollableColumnNotLargeEnoughToScrollGivesCorrectPositionAndSize(itemSize: Dp) {
+    @Test
+    fun reversedScrollableColumnNotLargeEnoughToScrollGivesCorrectPositionAndSize() {
+        scrollableColumnNotLargeEnoughToScroll(
+            {
+                Box(Modifier.requiredSize(itemSizeDp))
+                Box(Modifier.requiredSize(itemSizeDp))
+                Box(Modifier.requiredSize(itemSizeDp))
+            },
+            Arrangement.spacedBy(itemSpacingDp),
+            reverseScrolling = true
+        )
+    }
+
+    private fun scrollableColumnNotLargeEnoughToScroll(
+        columnContent: @Composable ColumnScope.() -> Unit,
+        verticalArrangement: Arrangement.Vertical = Arrangement.Top,
+        reverseScrolling: Boolean = false
+    ) {
         lateinit var state: ScrollState
         lateinit var positionIndicatorState: PositionIndicatorState
         var viewPortHeight = 0
@@ -802,66 +590,27 @@
                 modifier = Modifier
                     .onSizeChanged { viewPortHeight = it.height }
                     .requiredSize(itemSizeDp * 3.5f + itemSpacingDp * 2.5f)
-                    .verticalScroll(state = state),
-                verticalArrangement = Arrangement.spacedBy(itemSpacingDp)
+                    .verticalScroll(state = state, reverseScrolling = reverseScrolling),
+                verticalArrangement = verticalArrangement
             ) {
-                Box(Modifier.requiredSize(itemSize))
-                Box(Modifier.requiredSize(itemSize))
-                Box(Modifier.requiredSize(itemSize))
+                columnContent()
             }
             PositionIndicator(
                 state = positionIndicatorState,
                 indicatorHeight = 50.dp,
                 indicatorWidth = 4.dp,
                 paddingHorizontal = 5.dp,
+                reverseDirection = reverseScrolling
             )
         }
 
         rule.runOnIdle {
             assertThat(
                 positionIndicatorState.positionFraction
-            ).isEqualTo(0)
+            ).isEqualTo(0f)
             assertThat(
                 positionIndicatorState.sizeFraction(viewPortHeight.toFloat())
-            ).isEqualTo(1)
-        }
-    }
-
-    @Test
-    fun reversedScrollableColumnNotLargeEnoughToScrollGivesCorrectPositionAndSize() {
-        lateinit var state: ScrollState
-        lateinit var positionIndicatorState: PositionIndicatorState
-        var viewPortHeight = 0
-        rule.setContent {
-            state = rememberScrollState()
-            positionIndicatorState = ScrollStateAdapter(scrollState = state)
-            Column(
-                modifier = Modifier
-                    .onSizeChanged { viewPortHeight = it.height }
-                    .requiredSize(itemSizeDp * 3.5f + itemSpacingDp * 2.5f)
-                    .verticalScroll(state = state, reverseScrolling = true),
-                verticalArrangement = Arrangement.spacedBy(itemSpacingDp)
-            ) {
-                Box(Modifier.requiredSize(itemSizeDp))
-                Box(Modifier.requiredSize(itemSizeDp))
-                Box(Modifier.requiredSize(itemSizeDp))
-            }
-            PositionIndicator(
-                state = positionIndicatorState,
-                indicatorHeight = 50.dp,
-                indicatorWidth = 4.dp,
-                paddingHorizontal = 5.dp,
-                reverseDirection = true,
-            )
-        }
-
-        rule.runOnIdle {
-            assertThat(
-                positionIndicatorState.positionFraction
-            ).isEqualTo(0)
-            assertThat(
-                positionIndicatorState.sizeFraction(viewPortHeight.toFloat())
-            ).isEqualTo(1)
+            ).isEqualTo(1f)
         }
     }
 
@@ -1011,7 +760,303 @@
     ) {
         assertThat(visibleItemsInfo.first().index).isEqualTo(firstItemIndex)
         assertThat(visibleItemsInfo.last().index).isEqualTo(lastItemIndex)
-        assertThat((viewPortHeight / 2f) >=
-            (visibleItemsInfo.last().offset + (visibleItemsInfo.last().size / 2)))
+        assertThat(
+            (viewPortHeight / 2f) >=
+                (visibleItemsInfo.last().offset + (visibleItemsInfo.last().size / 2))
+        )
     }
 }
+
+/**
+ * Tests for PositionIndicator api which uses deprecated ScalingLazyColumn
+ * from androidx.wear.compose.material package.
+ */
+@MediumTest
+@Suppress("DEPRECATION")
+@RunWith(AndroidJUnit4::class)
+public class PositionIndicatorWithMaterialSLCTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    private var itemSizePx: Int = 50
+    private var itemSizeDp: Dp = Dp.Infinity
+    private var itemSpacingPx = 6
+    private var itemSpacingDp: Dp = Dp.Infinity
+
+    @Before
+    fun before() {
+        with(rule.density) {
+            itemSizeDp = itemSizePx.toDp()
+            itemSpacingDp = itemSpacingPx.toDp()
+        }
+    }
+
+    @Test
+    fun emptyScalingLazyColumnGivesCorrectPositionAndSize() {
+        scalingLazyColumnNotLargeEnoughToScroll(
+            Arrangement.spacedBy(itemSpacingDp)
+        ) {}
+    }
+
+    @Test
+    fun scalingLazyColumnNotLargeEnoughToScrollGivesCorrectPositionAndSize() {
+        scalingLazyColumnNotLargeEnoughToScrollGivesCorrectPositionAndSize(itemSizeDp)
+    }
+
+    @Test
+    fun scalingLazyColumnNotLargeEnoughToScrollGivesCorrectPositionAndSizeForZeroSizeItems() {
+        scalingLazyColumnNotLargeEnoughToScrollGivesCorrectPositionAndSize(0.dp)
+    }
+
+    private fun scalingLazyColumnNotLargeEnoughToScrollGivesCorrectPositionAndSize(itemSize: Dp) {
+        scalingLazyColumnNotLargeEnoughToScroll(
+            Arrangement.spacedBy(itemSpacingDp),
+            autoCentering = null
+        ) {
+            items(3) {
+                Box(Modifier.requiredSize(itemSize))
+            }
+        }
+    }
+
+    @Test
+    fun scalingLazyColumnNotLargeEnoughToScrollSwapVerticalAlignmentGivesCorrectPositionAndSize() {
+        scalingLazyColumnNotLargeEnoughToScroll(
+            Arrangement.spacedBy(
+                space = itemSpacingDp,
+                alignment = Alignment.Bottom
+            )
+        ) {
+            items(3) {
+                Box(Modifier.requiredSize(itemSizeDp))
+            }
+        }
+    }
+
+    fun scrollableScalingLazyColumnGivesCorrectPositionAndSize() {
+        scrollableScalingLazyColumnPositionAndSize(
+            enableAutoCentering = true,
+            contentPaddingPx = 0
+        )
+    }
+
+    @Test
+    fun scrollableScalingLazyColumnGivesCorrectPositionAndSizeWithContentPadding() {
+        scrollableScalingLazyColumnPositionAndSize(
+            enableAutoCentering = true,
+            contentPaddingPx = itemSizePx + itemSpacingPx
+        )
+    }
+
+    @Test
+    fun scrollableScalingLazyColumnGivesCorrectPositionAndSizeWithContentPaddingNoAutoCenter() {
+        scrollableScalingLazyColumnPositionAndSize(
+            enableAutoCentering = false,
+            contentPaddingPx = itemSizePx + itemSpacingPx
+        )
+    }
+
+    private fun scrollableScalingLazyColumnPositionAndSize(
+        enableAutoCentering: Boolean,
+        contentPaddingPx: Int
+    ) {
+        lateinit var state: androidx.wear.compose.material.ScalingLazyListState
+        lateinit var positionIndicatorState: PositionIndicatorState
+        var viewPortHeight = 0
+        rule.setContent {
+            state =
+                androidx.wear.compose.material.rememberScalingLazyListState(
+                    initialCenterItemIndex = 0
+                )
+            positionIndicatorState = MaterialScalingLazyColumnStateAdapter(state)
+            ScalingLazyColumn(
+                state = state,
+                verticalArrangement = Arrangement.spacedBy(itemSpacingDp),
+                modifier = Modifier
+                    .onSizeChanged { viewPortHeight = it.height }
+                    .requiredHeight(
+                        // Exactly the right size to hold 3 items with spacing
+                        itemSizeDp * 3f + itemSpacingDp * 2f
+                    )
+                    .background(Color.Black),
+                scalingParams =
+                androidx.wear.compose.material.ScalingLazyColumnDefaults.scalingParams(
+                    edgeScale = 1.0f
+                ),
+                autoCentering = if (enableAutoCentering)
+                    androidx.wear.compose.material.AutoCenteringParams(itemIndex = 0) else null,
+                contentPadding = with(LocalDensity.current) {
+                    PaddingValues(contentPaddingPx.toDp())
+                }
+            ) {
+                items(5) {
+                    Box(
+                        Modifier
+                            .requiredSize(itemSizeDp)
+                            .border(BorderStroke(1.dp, Color.Green))
+                    )
+                }
+            }
+            PositionIndicator(
+                state = positionIndicatorState,
+                indicatorHeight = 50.dp,
+                indicatorWidth = 4.dp,
+                paddingHorizontal = 5.dp,
+            )
+        }
+
+        rule.runOnIdle {
+            // Scroll forwards so that item with index 2 is in the center of the viewport
+            runBlocking {
+                state.scrollBy((itemSizePx.toFloat() + itemSpacingPx.toFloat()) * 2f)
+            }
+
+            state.layoutInfo.assertWhollyVisibleItems(
+                firstItemIndex = 1, lastItemIndex = 3,
+                viewPortHeight = viewPortHeight
+            )
+
+            // And that the indicator is at position 0.5 and of expected size
+            assertThat(
+                positionIndicatorState.positionFraction
+            ).isWithin(0.05f).of(0.5f)
+            assertThat(
+                positionIndicatorState.sizeFraction(viewPortHeight.toFloat())
+            ).isWithin(0.05f).of(0.6f)
+        }
+    }
+
+    @Test
+    fun emptyReverseLayoutScalingLazyColumnGivesCorrectPositionAndSize() {
+        scalingLazyColumnNotLargeEnoughToScroll(
+            Arrangement.spacedBy(itemSpacingDp),
+            reverseLayout = true
+        ) {}
+    }
+
+    @Test
+    fun reverseLayoutScalingLazyColumnNotLargeEnoughToScrollGivesCorrectPositionAndSize() {
+        scalingLazyColumnNotLargeEnoughToScroll(
+            Arrangement.spacedBy(
+                space = itemSpacingDp,
+                alignment = Alignment.Bottom
+            ),
+            autoCentering = null,
+            reverseLayout = true
+        ) {
+            items(3) {
+                Box(Modifier.requiredSize(itemSizeDp))
+            }
+        }
+    }
+
+    private fun scalingLazyColumnNotLargeEnoughToScroll(
+        verticalArrangement: Arrangement.Vertical,
+        reverseLayout: Boolean = false,
+        autoCentering: androidx.wear.compose.material.AutoCenteringParams? =
+            androidx.wear.compose.material.AutoCenteringParams(),
+        slcContent: androidx.wear.compose.material.ScalingLazyListScope.() -> Unit
+    ) {
+        lateinit var state: androidx.wear.compose.material.ScalingLazyListState
+        lateinit var positionIndicatorState: PositionIndicatorState
+        var viewPortHeight = 0
+        rule.setContent {
+            state = androidx.wear.compose.material.rememberScalingLazyListState()
+            positionIndicatorState = MaterialScalingLazyColumnStateAdapter(state)
+            ScalingLazyColumn(
+                state = state,
+                verticalArrangement = verticalArrangement,
+                reverseLayout = reverseLayout,
+                modifier = Modifier
+                    .onSizeChanged { viewPortHeight = it.height }
+                    .requiredSize(itemSizeDp * 3.5f + itemSpacingDp * 2.5f),
+                autoCentering = autoCentering
+            ) {
+                slcContent(this)
+            }
+            PositionIndicator(
+                state = positionIndicatorState,
+                indicatorHeight = 50.dp,
+                indicatorWidth = 4.dp,
+                paddingHorizontal = 5.dp,
+            )
+        }
+
+        rule.runOnIdle {
+            assertThat(
+                positionIndicatorState.positionFraction
+            ).isEqualTo(0f)
+            assertThat(
+                positionIndicatorState.sizeFraction(viewPortHeight.toFloat())
+            ).isEqualTo(1f)
+        }
+    }
+
+    @Test
+    fun reverseLayoutScrollableScalingLazyColumnGivesCorrectPositionAndSize() {
+        lateinit var state: androidx.wear.compose.material.ScalingLazyListState
+        lateinit var positionIndicatorState: PositionIndicatorState
+        var viewPortHeight = 0
+        rule.setContent {
+            state = androidx.wear.compose.material.rememberScalingLazyListState()
+            positionIndicatorState = MaterialScalingLazyColumnStateAdapter(state)
+            ScalingLazyColumn(
+                state = state,
+                verticalArrangement = Arrangement.spacedBy(itemSpacingDp),
+                reverseLayout = true,
+                modifier = Modifier
+                    .onSizeChanged { viewPortHeight = it.height }
+                    .requiredHeight(
+                        // Exactly the right size to hold 3 items with spacing
+                        itemSizeDp * 3f + itemSpacingDp * 2f
+                    )
+                    .background(Color.DarkGray),
+                scalingParams = androidx.wear.compose.material.ScalingLazyColumnDefaults
+                    .scalingParams(edgeScale = 1.0f),
+                autoCentering = null
+            ) {
+                items(5) {
+                    Box(Modifier.requiredSize(itemSizeDp))
+                }
+            }
+            PositionIndicator(
+                state = positionIndicatorState,
+                indicatorHeight = 50.dp,
+                indicatorWidth = 4.dp,
+                paddingHorizontal = 5.dp,
+            )
+        }
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollBy(itemSizePx.toFloat() + itemSpacingPx.toFloat())
+            }
+
+            state.layoutInfo.assertWhollyVisibleItems(
+                firstItemIndex = 1, lastItemIndex = 3,
+                viewPortHeight = viewPortHeight
+            )
+
+            // And that the indicator is at position 0.5 and of expected size
+            assertThat(
+                positionIndicatorState.positionFraction
+            ).isWithin(0.05f).of(0.5f)
+            assertThat(
+                positionIndicatorState.sizeFraction(viewPortHeight.toFloat())
+            ).isWithin(0.05f).of(0.6f)
+        }
+    }
+
+    private fun androidx.wear.compose.material.ScalingLazyListLayoutInfo.assertWhollyVisibleItems(
+        firstItemIndex: Int,
+        lastItemIndex: Int,
+        viewPortHeight: Int
+    ) {
+        assertThat(visibleItemsInfo.first().index).isEqualTo(firstItemIndex)
+        assertThat(visibleItemsInfo.last().index).isEqualTo(lastItemIndex)
+        assertThat(
+            (viewPortHeight / 2f) >=
+                (visibleItemsInfo.last().offset + (visibleItemsInfo.last().size / 2))
+        )
+    }
+}
\ No newline at end of file
diff --git a/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ScaffoldTest.kt b/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ScaffoldTest.kt
index e4261bf..c6c1ed6 100644
--- a/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ScaffoldTest.kt
+++ b/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ScaffoldTest.kt
@@ -26,6 +26,8 @@
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
@@ -107,7 +109,9 @@
             val scrollState = rememberScalingLazyListState()
 
             Scaffold(
-                modifier = Modifier.testTag(TEST_TAG).background(Color.Black),
+                modifier = Modifier
+                    .testTag(TEST_TAG)
+                    .background(Color.Black),
                 timeText = { Text(TIME_TEXT_MESSAGE) },
                 vignette = {
                     if (showVignette.value) {
diff --git a/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ScalingLazyColumnIndexedTest.kt b/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ScalingLazyColumnIndexedTest.kt
index 1a3cbe7..5eec897 100644
--- a/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ScalingLazyColumnIndexedTest.kt
+++ b/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ScalingLazyColumnIndexedTest.kt
@@ -34,6 +34,7 @@
 import org.junit.Rule
 import org.junit.Test
 
+@Suppress("DEPRECATION")
 class ScalingLazyColumnIndexedTest {
 
     @get:Rule
diff --git a/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ScalingLazyColumnTest.kt b/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ScalingLazyColumnTest.kt
index b6f60a4..2549f15 100644
--- a/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ScalingLazyColumnTest.kt
+++ b/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ScalingLazyColumnTest.kt
@@ -57,6 +57,7 @@
 import kotlin.math.roundToInt
 import kotlinx.coroutines.runBlocking
 
+@Suppress("DEPRECATION")
 @MediumTest
 @RunWith(AndroidJUnit4::class)
 // These tests are in addition to ScalingLazyListLayoutInfoTest which handles scroll events at an
diff --git a/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ScalingLazyListLayoutInfoTest.kt b/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ScalingLazyListLayoutInfoTest.kt
index a9d571a..82dcfc3 100644
--- a/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ScalingLazyListLayoutInfoTest.kt
+++ b/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ScalingLazyListLayoutInfoTest.kt
@@ -50,6 +50,7 @@
 import kotlin.math.roundToInt
 import org.junit.Ignore
 
+@Suppress("DEPRECATION")
 @MediumTest
 @RunWith(AndroidJUnit4::class)
 public class ScalingLazyListLayoutInfoTest {
diff --git a/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ScrollAwayTest.kt b/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ScrollAwayTest.kt
index 86a4f4b..edbe7fc 100644
--- a/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ScrollAwayTest.kt
+++ b/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/ScrollAwayTest.kt
@@ -36,6 +36,10 @@
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
+import androidx.wear.compose.foundation.lazy.AutoCenteringParams
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.ScalingLazyListState
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -83,6 +87,49 @@
         rule.onNodeWithTag(TIME_TEXT_TAG).assertIsDisplayed()
     }
 
+    @Suppress("DEPRECATION")
+    @Test
+    fun hidesTimeTextWithMaterialScalingLazyColumn() {
+        lateinit var scrollState: androidx.wear.compose.material.ScalingLazyListState
+        rule.setContentWithTheme {
+            scrollState =
+                androidx.wear.compose.material.rememberScalingLazyListState(
+                    initialCenterItemIndex = 1,
+                    initialCenterItemScrollOffset = 0
+                )
+            MaterialScalingLazyColumnTest(itemIndex = 1, offset = 0.dp, scrollState)
+        }
+
+        rule.onNodeWithTag(SCROLL_TAG).performTouchInput { swipeUp() }
+
+        rule.onNodeWithTag(TIME_TEXT_TAG).assertIsNotDisplayed()
+    }
+
+    @Suppress("DEPRECATION")
+    @Test
+    fun showsTimeTextWithMaterialScalingLazyColumnIfItemIndexInvalid() {
+        val scrollAwayItemIndex = 10
+        lateinit var scrollState: androidx.wear.compose.material.ScalingLazyListState
+        rule.setContentWithTheme {
+            scrollState =
+                androidx.wear.compose.material.rememberScalingLazyListState(
+                    initialCenterItemIndex = 1,
+                    initialCenterItemScrollOffset = 0
+                )
+            MaterialScalingLazyColumnTest(
+                itemIndex = scrollAwayItemIndex,
+                offset = 0.dp,
+                scrollState
+            )
+        }
+
+        rule.onNodeWithTag(SCROLL_TAG).performTouchInput { swipeUp() }
+
+        // b/256166359 - itemIndex > number of items in the list.
+        // ScrollAway should default to always showing TimeText
+        rule.onNodeWithTag(TIME_TEXT_TAG).assertIsDisplayed()
+    }
+
     @Composable
     private fun ScalingLazyColumnTest(
         itemIndex: Int,
@@ -91,7 +138,9 @@
     ) {
         WithTouchSlop(0f) {
             Scaffold(
-                modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background),
+                modifier = Modifier
+                    .fillMaxSize()
+                    .background(MaterialTheme.colors.background),
                 timeText = {
                     TimeText(
                         modifier = Modifier
@@ -122,6 +171,50 @@
         }
     }
 
+    @Suppress("DEPRECATION")
+    @Composable
+    private fun MaterialScalingLazyColumnTest(
+        itemIndex: Int,
+        offset: Dp,
+        scrollState: androidx.wear.compose.material.ScalingLazyListState
+    ) {
+        WithTouchSlop(0f) {
+            Scaffold(
+                modifier = Modifier
+                    .fillMaxSize()
+                    .background(MaterialTheme.colors.background),
+                timeText = {
+                    TimeText(
+                        modifier = Modifier
+                            .scrollAway(
+                                scrollState = scrollState,
+                                itemIndex = itemIndex,
+                                offset = offset,
+                            )
+                            .testTag(TIME_TEXT_TAG)
+                    )
+                },
+            ) {
+                ScalingLazyColumn(
+                    contentPadding = PaddingValues(10.dp),
+                    state = scrollState,
+                    autoCentering = androidx.wear.compose.material.AutoCenteringParams(
+                        itemIndex = 1, itemOffset = 0
+                    ),
+                    modifier = Modifier.testTag(SCROLL_TAG)
+                ) {
+                    item {
+                        ListHeader { Text("Chips") }
+                    }
+
+                    items(5) { i ->
+                        ChipTest(Modifier.fillParentMaxHeight(0.5f), i)
+                    }
+                }
+            }
+        }
+    }
+
     @Test
     fun hidesTimeTextWithLazyColumn() {
         lateinit var scrollState: LazyListState
@@ -149,7 +242,8 @@
                     initialFirstVisibleItemIndex = 1,
                 )
             LazyColumnTest(
-                itemIndex = scrollAwayItemIndex, offset = 0.dp, scrollState)
+                itemIndex = scrollAwayItemIndex, offset = 0.dp, scrollState
+            )
         }
 
         rule.onNodeWithTag(SCROLL_TAG).performTouchInput { swipeUp() }
@@ -167,7 +261,9 @@
     ) {
         WithTouchSlop(0f) {
             Scaffold(
-                modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background),
+                modifier = Modifier
+                    .fillMaxSize()
+                    .background(MaterialTheme.colors.background),
                 timeText = {
                     TimeText(
                         modifier = Modifier
diff --git a/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/dialog/DialogWithMaterialSlcTest.kt b/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/dialog/DialogWithMaterialSlcTest.kt
new file mode 100644
index 0000000..1eddea4
--- /dev/null
+++ b/wear/compose/compose-material/src/androidAndroidTest/kotlin/androidx/wear/compose/material/dialog/DialogWithMaterialSlcTest.kt
@@ -0,0 +1,1131 @@
+/*
+ * 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.material.dialog
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+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.testutils.assertIsEqualTo
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.hasClickAction
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.SdkSuppress
+import androidx.wear.compose.material.Button
+import androidx.wear.compose.material.Chip
+import androidx.wear.compose.material.LocalContentColor
+import androidx.wear.compose.material.LocalTextStyle
+import androidx.wear.compose.material.MaterialTheme
+import androidx.wear.compose.material.TEST_TAG
+import androidx.wear.compose.material.TestImage
+import androidx.wear.compose.material.Text
+import androidx.wear.compose.material.assertContainsColor
+import androidx.wear.compose.material.setContentWithTheme
+import androidx.wear.compose.material.setContentWithThemeForSizeAssertions
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * These tests were copied from DialogTest.kt for support of deprecated Dialogs
+ */
+
+@Suppress("DEPRECATION")
+class DialogWithMaterialSlcBehaviourTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun supports_testtag_on_alert_with_buttons() {
+        rule.setContentWithTheme {
+            AlertWithMaterialSlc(
+                title = {},
+                negativeButton = {},
+                positiveButton = {},
+                modifier = Modifier.testTag(TEST_TAG),
+            )
+        }
+
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    fun supports_testtag_on_alert_with_chips() {
+        rule.setContentWithTheme {
+            AlertWithMaterialSlc(
+                title = {},
+                message = {},
+                content = {},
+                modifier = Modifier.testTag(TEST_TAG),
+            )
+        }
+
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    fun supports_testtag_on_ConfirmationWithMaterialSlc() {
+        rule.setContentWithTheme {
+            ConfirmationWithMaterialSlc(
+                onTimeout = {},
+                icon = {},
+                content = {},
+                modifier = Modifier.testTag(TEST_TAG),
+            )
+        }
+
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    fun displays_icon_on_alert_with_buttons() {
+        rule.setContentWithTheme {
+            AlertWithMaterialSlc(
+                icon = { TestImage(TEST_TAG) },
+                title = {},
+                negativeButton = {},
+                positiveButton = {},
+            )
+        }
+
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    fun displays_icon_on_alert_with_chips() {
+        rule.setContentWithTheme {
+            AlertWithMaterialSlc(
+                icon = { TestImage(TEST_TAG) },
+                title = {},
+                message = {},
+                content = {},
+            )
+        }
+
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    fun displays_icon_on_ConfirmationWithMaterialSlc() {
+        rule.setContentWithTheme {
+            ConfirmationWithMaterialSlc(
+                onTimeout = {},
+                icon = { TestImage(TEST_TAG) },
+                content = {},
+            )
+        }
+
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    fun displays_title_on_alert_with_buttons() {
+        rule.setContentWithTheme {
+            AlertWithMaterialSlc(
+                title = { Text("Text", modifier = Modifier.testTag(TEST_TAG)) },
+                negativeButton = {},
+                positiveButton = {},
+            )
+        }
+
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    fun displays_title_on_alert_with_chips() {
+        rule.setContentWithTheme {
+            AlertWithMaterialSlc(
+                icon = {},
+                title = { Text("Text", modifier = Modifier.testTag(TEST_TAG)) },
+                message = {},
+                content = {},
+            )
+        }
+
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    fun displays_title_on_ConfirmationWithMaterialSlc() {
+        rule.setContentWithTheme {
+            ConfirmationWithMaterialSlc(
+                onTimeout = {},
+                icon = {},
+                content = { Text("Text", modifier = Modifier.testTag(TEST_TAG)) },
+            )
+        }
+
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    fun displays_bodymessage_on_alert_with_buttons() {
+        rule.setContentWithTheme {
+            AlertWithMaterialSlc(
+                title = {},
+                negativeButton = {},
+                positiveButton = {},
+                content = { Text("Text", modifier = Modifier.testTag(TEST_TAG)) },
+            )
+        }
+
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    fun displays_bodymessage_on_alert_with_chips() {
+        rule.setContentWithTheme {
+            AlertWithMaterialSlc(
+                icon = {},
+                title = {},
+                message = { Text("Text", modifier = Modifier.testTag(TEST_TAG)) },
+                content = {},
+            )
+        }
+
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    fun displays_buttons_on_alert_with_buttons() {
+        val buttonTag1 = "Button1"
+        val buttonTag2 = "Button2"
+
+        rule.setContentWithTheme {
+            AlertWithMaterialSlc(
+                title = {},
+                negativeButton = {
+                    Button(onClick = {}, modifier = Modifier.testTag(buttonTag1), content = {})
+                },
+                positiveButton = {
+                    Button(onClick = {}, modifier = Modifier.testTag(buttonTag2), content = {})
+                },
+                content = {},
+            )
+        }
+
+        rule.onNodeWithTag(buttonTag1).assertExists()
+        rule.onNodeWithTag(buttonTag2).assertExists()
+    }
+
+    @Test
+    fun supports_swipetodismiss_on_wrapped_alertdialog_with_buttons() {
+        rule.setContentWithTheme {
+            Box {
+                var showDialog by remember { mutableStateOf(true) }
+                Column(
+                    modifier = Modifier.fillMaxSize(),
+                    verticalArrangement = Arrangement.Center,
+                    horizontalAlignment = Alignment.CenterHorizontally
+                ) {
+                    Text("Start Screen")
+                }
+                Dialog(
+                    showDialog = showDialog,
+                    onDismissRequest = { showDialog = false },
+                ) {
+                    AlertWithMaterialSlc(
+                        title = {},
+                        negativeButton = {
+                            Button(onClick = {}, content = {})
+                        },
+                        positiveButton = {
+                            Button(onClick = {}, content = {})
+                        },
+                        content = { Text("Dialog", modifier = Modifier.testTag(TEST_TAG)) },
+                    )
+                }
+            }
+        }
+
+        rule.onNodeWithTag(TEST_TAG).performTouchInput({ swipeRight() })
+        rule.onNodeWithTag(TEST_TAG).assertDoesNotExist()
+    }
+
+    @Test
+    fun supports_swipetodismiss_on_wrapped_alertdialog_with_chips() {
+        rule.setContentWithTheme {
+            Box {
+                var showDialog by remember { mutableStateOf(true) }
+                Column(
+                    modifier = Modifier.fillMaxSize(),
+                    verticalArrangement = Arrangement.Center,
+                    horizontalAlignment = Alignment.CenterHorizontally
+                ) {
+                    Text("Label")
+                }
+                Dialog(
+                    showDialog = showDialog,
+                    onDismissRequest = { showDialog = false },
+                ) {
+                    AlertWithMaterialSlc(
+                        icon = {},
+                        title = {},
+                        message = { Text("Text", modifier = Modifier.testTag(TEST_TAG)) },
+                        content = {},
+                    )
+                }
+            }
+        }
+
+        rule.onNodeWithTag(TEST_TAG).performTouchInput({ swipeRight() })
+        rule.onNodeWithTag(TEST_TAG).assertDoesNotExist()
+    }
+
+    @Test
+    fun supports_swipetodismiss_on_wrapped_confirmationdialog() {
+        rule.setContentWithTheme {
+            Box {
+                var showDialog by remember { mutableStateOf(true) }
+                Column(
+                    modifier = Modifier.fillMaxSize(),
+                    verticalArrangement = Arrangement.Center,
+                    horizontalAlignment = Alignment.CenterHorizontally
+                ) {
+                    Text("Label")
+                }
+                Dialog(
+                    showDialog = showDialog,
+                    onDismissRequest = { showDialog = false },
+                ) {
+                    ConfirmationWithMaterialSlc(
+                        onTimeout = { showDialog = false },
+                        icon = {},
+                        content = { Text("Dialog", modifier = Modifier.testTag(TEST_TAG)) },
+                    )
+                }
+            }
+        }
+
+        rule.onNodeWithTag(TEST_TAG).performTouchInput({ swipeRight() })
+        rule.onNodeWithTag(TEST_TAG).assertDoesNotExist()
+    }
+
+    @Test
+    fun shows_dialog_when_showdialog_equals_true() {
+        rule.setContentWithTheme {
+            Box {
+                var showDialog by remember { mutableStateOf(false) }
+                Column(
+                    modifier = Modifier.fillMaxSize(),
+                    verticalArrangement = Arrangement.Center,
+                    horizontalAlignment = Alignment.CenterHorizontally
+                ) {
+                    Chip(onClick = { showDialog = true }, label = { Text("Show") })
+                }
+                Dialog(
+                    showDialog = showDialog,
+                    onDismissRequest = { showDialog = false },
+                ) {
+                    AlertWithMaterialSlc(
+                        icon = {},
+                        title = {},
+                        message = { Text("Text", modifier = Modifier.testTag(TEST_TAG)) },
+                        content = {},
+                    )
+                }
+            }
+        }
+
+        rule.onNode(hasClickAction()).performClick()
+        rule.onNodeWithTag(TEST_TAG).assertExists()
+    }
+
+    @Test
+    fun calls_ondismissrequest_when_dialog_is_swiped() {
+        val dismissedText = "Dismissed"
+        rule.setContentWithTheme {
+            Box {
+                var dismissed by remember { mutableStateOf(false) }
+                Column(
+                    modifier = Modifier.fillMaxSize(),
+                    verticalArrangement = Arrangement.Center,
+                    horizontalAlignment = Alignment.CenterHorizontally
+                ) {
+                    Text(if (dismissed) dismissedText else "Label")
+                }
+                Dialog(
+                    showDialog = !dismissed,
+                    onDismissRequest = { dismissed = true },
+                ) {
+                    AlertWithMaterialSlc(
+                        icon = {},
+                        title = {},
+                        message = { Text("Text", modifier = Modifier.testTag(TEST_TAG)) },
+                        content = {},
+                    )
+                }
+            }
+        }
+
+        rule.onNodeWithTag(TEST_TAG).performTouchInput({ swipeRight() })
+        rule.onNodeWithText(dismissedText).assertExists()
+    }
+}
+
+@Suppress("DEPRECATION")
+class DialogWithMaterialSlcContentSizeAndPositionTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun spaces_icon_and_title_correctly_on_alert_with_buttons() {
+        rule
+            .setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
+                AlertWithMaterialSlc(
+                    icon = { TestImage(ICON_TAG) },
+                    title = { Text("Title", modifier = Modifier.testTag(TITLE_TAG)) },
+                    negativeButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(BUTTON_TAG)) {}
+                    },
+                    positiveButton = { Button(onClick = {}) {} },
+                    verticalArrangement = Arrangement.spacedBy(0.dp, Alignment.CenterVertically),
+                    modifier = Modifier.testTag(TEST_TAG),
+                )
+            }
+
+        val iconBottom = rule.onNodeWithTag(ICON_TAG).getUnclippedBoundsInRoot().bottom
+        val titleTop = rule.onNodeWithTag(TITLE_TAG).getUnclippedBoundsInRoot().top
+        titleTop.assertIsEqualTo(iconBottom + DialogDefaults.IconSpacing)
+    }
+
+    @Test
+    fun spaces_title_and_buttons_correctly_on_alert_with_buttons() {
+        var titlePadding = 0.dp
+
+        rule
+            .setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
+                titlePadding = DialogDefaults.TitlePadding.calculateBottomPadding()
+                AlertWithMaterialSlc(
+                    icon = { TestImage(ICON_TAG) },
+                    title = { Text("Title", modifier = Modifier.testTag(TITLE_TAG)) },
+                    negativeButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(BUTTON_TAG)) {}
+                    },
+                    positiveButton = { Button(onClick = {}) {} },
+                    verticalArrangement = Arrangement.spacedBy(0.dp, Alignment.CenterVertically),
+                    modifier = Modifier.testTag(TEST_TAG),
+                )
+            }
+
+        val titleBottom = rule.onNodeWithTag(TITLE_TAG).getUnclippedBoundsInRoot().bottom
+        val buttonTop = rule.onNodeWithTag(BUTTON_TAG).getUnclippedBoundsInRoot().top
+        buttonTop.assertIsEqualTo(titleBottom + titlePadding)
+    }
+
+    @Test
+    fun spaces_icon_and_title_correctly_on_alert_with_chips() {
+        rule
+            .setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
+                AlertWithMaterialSlc(
+                    icon = { TestImage(ICON_TAG) },
+                    title = { Text("Title", modifier = Modifier.testTag(TITLE_TAG)) },
+                    content = {
+                        item {
+                            Chip(
+                                label = { Text("Chip") },
+                                onClick = {},
+                                modifier = Modifier.testTag(CHIP_TAG)
+                            )
+                        }
+                    },
+                    verticalArrangement = Arrangement.spacedBy(0.dp, Alignment.CenterVertically),
+                    modifier = Modifier.testTag(TEST_TAG),
+                )
+            }
+
+        val iconBottom = rule.onNodeWithTag(ICON_TAG).getUnclippedBoundsInRoot().bottom
+        val titleTop = rule.onNodeWithTag(TITLE_TAG).getUnclippedBoundsInRoot().top
+        titleTop.assertIsEqualTo(iconBottom + DialogDefaults.IconSpacing)
+    }
+
+    @Test
+    fun spaces_title_and_chips_correctly_on_alert_with_chips() {
+        var titlePadding = 0.dp
+
+        rule
+            .setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
+                titlePadding = DialogDefaults.TitlePadding.calculateBottomPadding()
+                AlertWithMaterialSlc(
+                    icon = { TestImage(ICON_TAG) },
+                    title = { Text("Title", modifier = Modifier.testTag(TITLE_TAG)) },
+                    content = {
+                        item {
+                            Chip(
+                                label = { Text("Chip") },
+                                onClick = {},
+                                modifier = Modifier.testTag(CHIP_TAG)
+                            )
+                        }
+                    },
+                    verticalArrangement = Arrangement.spacedBy(0.dp, Alignment.CenterVertically),
+                    modifier = Modifier.testTag(TEST_TAG),
+                )
+            }
+
+        val titleBottom = rule.onNodeWithTag(TITLE_TAG).getUnclippedBoundsInRoot().bottom
+        val chipTop = rule.onNodeWithTag(CHIP_TAG).getUnclippedBoundsInRoot().top
+        chipTop.assertIsEqualTo(titleBottom + titlePadding)
+    }
+
+    @Test
+    fun spaces_icon_and_title_correctly_on_ConfirmationWithMaterialSlc() {
+        rule
+            .setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
+                ConfirmationWithMaterialSlc(
+                    onTimeout = {},
+                    icon = { TestImage(ICON_TAG) },
+                    content = { Text("Title", modifier = Modifier.testTag(TITLE_TAG)) },
+                    verticalArrangement = Arrangement.spacedBy(0.dp, Alignment.CenterVertically),
+                    modifier = Modifier.testTag(TEST_TAG),
+                )
+            }
+
+        val iconBottom = rule.onNodeWithTag(ICON_TAG).getUnclippedBoundsInRoot().bottom
+        val titleTop = rule.onNodeWithTag(TITLE_TAG).getUnclippedBoundsInRoot().top
+        titleTop.assertIsEqualTo(iconBottom + DialogDefaults.IconSpacing)
+    }
+
+    @Test
+    fun spaces_title_and_body_correctly_on_alert_with_buttons() {
+        var titleSpacing = 0.dp
+
+        rule
+            .setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
+                titleSpacing = DialogDefaults.TitlePadding.calculateBottomPadding()
+                AlertWithMaterialSlc(
+                    title = { Text("Title", modifier = Modifier.testTag(TITLE_TAG)) },
+                    negativeButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(BUTTON_TAG)) {}
+                    },
+                    positiveButton = { Button(onClick = {}) {} },
+                    content = { Text("Body", modifier = Modifier.testTag(BODY_TAG)) },
+                    verticalArrangement = Arrangement.spacedBy(0.dp, Alignment.CenterVertically),
+                    modifier = Modifier.testTag(TEST_TAG),
+                )
+            }
+
+        val titleBottom = rule.onNodeWithTag(TITLE_TAG).getUnclippedBoundsInRoot().bottom
+        val bodyTop = rule.onNodeWithTag(BODY_TAG).getUnclippedBoundsInRoot().top
+        bodyTop.assertIsEqualTo(titleBottom + titleSpacing)
+    }
+
+    @Test
+    fun spaces_title_and_body_correctly_on_alert_with_chips() {
+        var titleSpacing = 0.dp
+
+        rule
+            .setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
+                titleSpacing = DialogDefaults.TitlePadding.calculateBottomPadding()
+                AlertWithMaterialSlc(
+                    icon = { TestImage(ICON_TAG) },
+                    title = { Text("Title", modifier = Modifier.testTag(TITLE_TAG)) },
+                    message = { Text("Message", modifier = Modifier.testTag(BODY_TAG)) },
+                    content = {
+                        item {
+                            Chip(
+                                label = { Text("Chip") },
+                                onClick = {},
+                                modifier = Modifier.testTag(CHIP_TAG)
+                            )
+                        }
+                    },
+                    verticalArrangement = Arrangement.spacedBy(0.dp, Alignment.CenterVertically),
+                    modifier = Modifier.testTag(TEST_TAG),
+                )
+            }
+
+        val titleBottom = rule.onNodeWithTag(TITLE_TAG).getUnclippedBoundsInRoot().bottom
+        val bodyTop = rule.onNodeWithTag(BODY_TAG).getUnclippedBoundsInRoot().top
+        bodyTop.assertIsEqualTo(titleBottom + titleSpacing)
+    }
+
+    @Test
+    fun spaces_body_and_buttons_correctly_on_alert_with_buttons() {
+        var bodyPadding = 0.dp
+
+        rule
+            .setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
+                bodyPadding = DialogDefaults.BodyPadding.calculateBottomPadding()
+                AlertWithMaterialSlc(
+                    icon = {},
+                    title = {},
+                    negativeButton = {
+                        Button(onClick = {}, modifier = Modifier.testTag(BUTTON_TAG)) {}
+                    },
+                    positiveButton = {
+                        Button(onClick = {}) {}
+                    },
+                    content = { Text("Body", modifier = Modifier.testTag(BODY_TAG)) },
+                    verticalArrangement = Arrangement.spacedBy(0.dp, Alignment.CenterVertically),
+                    modifier = Modifier.testTag(TEST_TAG),
+                )
+            }
+
+        val bodyBottom = rule.onNodeWithTag(BODY_TAG).getUnclippedBoundsInRoot().bottom
+        val buttonTop = rule.onNodeWithTag(BUTTON_TAG).getUnclippedBoundsInRoot().top
+        buttonTop.assertIsEqualTo(bodyBottom + bodyPadding)
+    }
+
+    @Test
+    fun spaces_body_and_chips_correctly_on_alert_with_chips() {
+        var bodyPadding = 0.dp
+
+        rule
+            .setContentWithThemeForSizeAssertions(useUnmergedTree = true) {
+                bodyPadding = DialogDefaults.BodyPadding.calculateBottomPadding()
+                AlertWithMaterialSlc(
+                    icon = { TestImage(ICON_TAG) },
+                    title = { Text("Title", modifier = Modifier.testTag(TITLE_TAG)) },
+                    message = { Text("Message", modifier = Modifier.testTag(BODY_TAG)) },
+                    content = {
+                        item {
+                            Chip(
+                                label = { Text("Chip") },
+                                onClick = {},
+                                modifier = Modifier.testTag(CHIP_TAG)
+                            )
+                        }
+                    },
+                    verticalArrangement = Arrangement.spacedBy(0.dp, Alignment.CenterVertically),
+                    modifier = Modifier.testTag(TEST_TAG),
+                )
+            }
+
+        val bodyBottom = rule.onNodeWithTag(BODY_TAG).getUnclippedBoundsInRoot().bottom
+        val chipTop = rule.onNodeWithTag(CHIP_TAG).getUnclippedBoundsInRoot().top
+        chipTop.assertIsEqualTo(bodyBottom + bodyPadding)
+    }
+}
+
+@Suppress("DEPRECATION")
+class DialogWithMaterialSlcContentColorTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun gives_icon_onbackground_on_alert_for_buttons() {
+        var expectedColor = Color.Transparent
+        var actualColor = Color.Transparent
+
+        rule.setContentWithTheme {
+            expectedColor = MaterialTheme.colors.onBackground
+            AlertWithMaterialSlc(
+                icon = { actualColor = LocalContentColor.current },
+                title = {},
+                negativeButton = {},
+                positiveButton = {},
+                content = {},
+            )
+        }
+
+        Assert.assertEquals(expectedColor, actualColor)
+    }
+
+    @Test
+    fun gives_icon_onbackground_on_alert_for_chips() {
+        var expectedColor = Color.Transparent
+        var actualColor = Color.Transparent
+
+        rule.setContentWithTheme {
+            expectedColor = MaterialTheme.colors.onBackground
+            AlertWithMaterialSlc(
+                icon = { actualColor = LocalContentColor.current },
+                title = {},
+                message = {},
+                content = {},
+            )
+        }
+
+        Assert.assertEquals(expectedColor, actualColor)
+    }
+
+    @Test
+    fun gives_icon_onbackground_on_ConfirmationWithMaterialSlc() {
+        var expectedColor = Color.Transparent
+        var actualColor = Color.Transparent
+
+        rule.setContentWithTheme {
+            expectedColor = MaterialTheme.colors.onBackground
+            ConfirmationWithMaterialSlc(
+                onTimeout = {},
+                icon = { actualColor = LocalContentColor.current },
+                content = {},
+            )
+        }
+
+        Assert.assertEquals(expectedColor, actualColor)
+    }
+
+    @Test
+    fun gives_custom_icon_on_alert_for_buttons() {
+        val overrideColor = Color.Yellow
+        var actualColor = Color.Transparent
+
+        rule.setContentWithTheme {
+            AlertWithMaterialSlc(
+                iconColor = overrideColor,
+                icon = { actualColor = LocalContentColor.current },
+                title = {},
+                negativeButton = {},
+                positiveButton = {},
+                content = {},
+            )
+        }
+
+        Assert.assertEquals(overrideColor, actualColor)
+    }
+
+    @Test
+    fun gives_custom_icon_on_alert_for_chips() {
+        val overrideColor = Color.Yellow
+        var actualColor = Color.Transparent
+
+        rule.setContentWithTheme {
+            AlertWithMaterialSlc(
+                iconColor = overrideColor,
+                icon = { actualColor = LocalContentColor.current },
+                title = {},
+                message = {},
+                content = {},
+            )
+        }
+
+        Assert.assertEquals(overrideColor, actualColor)
+    }
+
+    @Test
+    fun gives_custom_icon_on_ConfirmationWithMaterialSlc() {
+        val overrideColor = Color.Yellow
+        var actualColor = Color.Transparent
+
+        rule.setContentWithTheme {
+            ConfirmationWithMaterialSlc(
+                onTimeout = {},
+                iconColor = overrideColor,
+                icon = { actualColor = LocalContentColor.current },
+                content = {},
+            )
+        }
+
+        Assert.assertEquals(overrideColor, actualColor)
+    }
+
+    @Test
+    fun gives_title_onbackground_on_alert_for_buttons() {
+        var expectedColor = Color.Transparent
+        var actualColor = Color.Transparent
+
+        rule.setContentWithTheme {
+            expectedColor = MaterialTheme.colors.onBackground
+            AlertWithMaterialSlc(
+                title = { actualColor = LocalContentColor.current },
+                negativeButton = {},
+                positiveButton = {},
+                content = {},
+            )
+        }
+
+        Assert.assertEquals(expectedColor, actualColor)
+    }
+
+    @Test
+    fun gives_title_onbackground_on_alert_for_chips() {
+        var expectedColor = Color.Transparent
+        var actualColor = Color.Transparent
+
+        rule.setContentWithTheme {
+            expectedColor = MaterialTheme.colors.onBackground
+            AlertWithMaterialSlc(
+                title = { actualColor = LocalContentColor.current },
+                message = {},
+                content = {},
+            )
+        }
+
+        Assert.assertEquals(expectedColor, actualColor)
+    }
+
+    @Test
+    fun gives_title_onbackground_on_ConfirmationWithMaterialSlc() {
+        var expectedColor = Color.Transparent
+        var actualColor = Color.Transparent
+
+        rule.setContentWithTheme {
+            expectedColor = MaterialTheme.colors.onBackground
+            ConfirmationWithMaterialSlc(
+                onTimeout = {},
+                content = { actualColor = LocalContentColor.current },
+            )
+        }
+
+        Assert.assertEquals(expectedColor, actualColor)
+    }
+
+    @Test
+    fun gives_custom_title_on_alert_for_buttons() {
+        val overrideColor = Color.Yellow
+        var actualColor = Color.Transparent
+
+        rule.setContentWithTheme {
+            AlertWithMaterialSlc(
+                titleColor = overrideColor,
+                title = { actualColor = LocalContentColor.current },
+                negativeButton = {},
+                positiveButton = {},
+                content = {},
+            )
+        }
+
+        Assert.assertEquals(overrideColor, actualColor)
+    }
+
+    @Test
+    fun gives_custom_title_on_alert_for_chips() {
+        val overrideColor = Color.Yellow
+        var actualColor = Color.Transparent
+
+        rule.setContentWithTheme {
+            AlertWithMaterialSlc(
+                titleColor = overrideColor,
+                title = { actualColor = LocalContentColor.current },
+                message = {},
+                content = {},
+            )
+        }
+
+        Assert.assertEquals(overrideColor, actualColor)
+    }
+
+    @Test
+    fun gives_custom_title_on_ConfirmationWithMaterialSlc() {
+        val overrideColor = Color.Yellow
+        var actualColor = Color.Transparent
+
+        rule.setContentWithTheme {
+            ConfirmationWithMaterialSlc(
+                onTimeout = {},
+                contentColor = overrideColor,
+                content = { actualColor = LocalContentColor.current },
+            )
+        }
+
+        Assert.assertEquals(overrideColor, actualColor)
+    }
+
+    @Test
+    fun gives_bodymessage_onbackground_on_alert_for_buttons() {
+        var expectedContentColor = Color.Transparent
+        var actualContentColor = Color.Transparent
+
+        rule.setContentWithTheme {
+            expectedContentColor = MaterialTheme.colors.onBackground
+            AlertWithMaterialSlc(
+                title = {},
+                negativeButton = {},
+                positiveButton = {},
+                content = { actualContentColor = LocalContentColor.current },
+            )
+        }
+
+        Assert.assertEquals(expectedContentColor, actualContentColor)
+    }
+
+    @Test
+    fun gives_bodymessage_onbackground_on_alert_for_chips() {
+        var expectedContentColor = Color.Transparent
+        var actualContentColor = Color.Transparent
+
+        rule.setContentWithTheme {
+            expectedContentColor = MaterialTheme.colors.onBackground
+            AlertWithMaterialSlc(
+                title = {},
+                message = { actualContentColor = LocalContentColor.current },
+                content = {},
+            )
+        }
+
+        Assert.assertEquals(expectedContentColor, actualContentColor)
+    }
+
+    @Test
+    fun gives_custom_bodymessage_on_alert_for_buttons() {
+        val overrideColor = Color.Yellow
+        var actualColor = Color.Transparent
+
+        rule.setContentWithTheme {
+            AlertWithMaterialSlc(
+                title = {},
+                negativeButton = {},
+                positiveButton = {},
+                contentColor = overrideColor,
+                content = { actualColor = LocalContentColor.current },
+            )
+        }
+
+        Assert.assertEquals(overrideColor, actualColor)
+    }
+
+    @Test
+    fun gives_custom_bodymessage_on_alert_for_chips() {
+        val overrideColor = Color.Yellow
+        var actualColor = Color.Transparent
+
+        rule.setContentWithTheme {
+            AlertWithMaterialSlc(
+                title = {},
+                messageColor = overrideColor,
+                message = { actualColor = LocalContentColor.current },
+                content = {},
+            )
+        }
+
+        Assert.assertEquals(overrideColor, actualColor)
+    }
+
+    @RequiresApi(Build.VERSION_CODES.O)
+    @Test
+    fun gives_correct_background_color_on_alert_for_buttons() {
+        verifyBackgroundColor(expected = { MaterialTheme.colors.background }) {
+            AlertWithMaterialSlc(
+                title = {},
+                negativeButton = {},
+                positiveButton = {},
+                content = {},
+                modifier = Modifier.testTag(TEST_TAG)
+            )
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.O)
+    @Test
+    fun gives_correct_background_color_on_alert_for_chips() {
+        verifyBackgroundColor(expected = { MaterialTheme.colors.background }) {
+            AlertWithMaterialSlc(
+                title = {},
+                message = {},
+                content = {},
+                modifier = Modifier.testTag(TEST_TAG)
+            )
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.O)
+    @Test
+    fun gives_correct_background_color_on_ConfirmationWithMaterialSlc() {
+        verifyBackgroundColor(expected = { MaterialTheme.colors.background }) {
+            ConfirmationWithMaterialSlc(
+                onTimeout = {},
+                content = {},
+                modifier = Modifier.testTag(TEST_TAG),
+            )
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun gives_custom_background_color_on_alert_for_buttons() {
+        val overrideColor = Color.Yellow
+
+        rule.setContentWithTheme {
+            AlertWithMaterialSlc(
+                title = {},
+                negativeButton = {},
+                positiveButton = {},
+                content = {},
+                backgroundColor = overrideColor,
+                modifier = Modifier.testTag(TEST_TAG),
+            )
+        }
+
+        rule.onNodeWithTag(TEST_TAG)
+            .captureToImage()
+            .assertContainsColor(overrideColor, 100.0f)
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun gives_custom_background_color_on_alert_for_chips() {
+        val overrideColor = Color.Yellow
+
+        rule.setContentWithTheme {
+            AlertWithMaterialSlc(
+                title = {},
+                message = {},
+                content = {},
+                backgroundColor = overrideColor,
+                modifier = Modifier.testTag(TEST_TAG),
+            )
+        }
+
+        rule.onNodeWithTag(TEST_TAG)
+            .captureToImage()
+            .assertContainsColor(overrideColor, 100.0f)
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun gives_custom_background_color_on_ConfirmationWithMaterialSlc() {
+        val overrideColor = Color.Yellow
+
+        rule.setContentWithTheme {
+            ConfirmationWithMaterialSlc(
+                onTimeout = {},
+                content = {},
+                backgroundColor = overrideColor,
+                modifier = Modifier.testTag(TEST_TAG),
+            )
+        }
+
+        rule.onNodeWithTag(TEST_TAG)
+            .captureToImage()
+            .assertContainsColor(overrideColor, 100.0f)
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    private fun verifyBackgroundColor(
+        expected: @Composable () -> Color,
+        content: @Composable () -> Unit
+    ) {
+        val testBackground = Color.White
+        var expectedBackground = Color.Transparent
+
+        rule.setContentWithTheme {
+            Box(modifier = Modifier
+                .fillMaxSize()
+                .background(testBackground)) {
+                expectedBackground = expected()
+                content()
+            }
+        }
+
+        rule.onNodeWithTag(TEST_TAG)
+            .captureToImage()
+            .assertContainsColor(expectedBackground, 100.0f)
+    }
+}
+
+@Suppress("DEPRECATION")
+class DialogWithMaterialSlcTextStyleTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun gives_title_correct_textstyle_on_alert_for_buttons() {
+        var actualTextStyle = TextStyle.Default
+        var expectedTextStyle = TextStyle.Default
+
+        rule.setContentWithTheme {
+            expectedTextStyle = MaterialTheme.typography.title3
+            AlertWithMaterialSlc(
+                title = { actualTextStyle = LocalTextStyle.current },
+                negativeButton = {},
+                positiveButton = {},
+            )
+        }
+
+        Assert.assertEquals(expectedTextStyle, actualTextStyle)
+    }
+
+    @Test
+    fun gives_title_correct_textstyle_on_alert_for_chips() {
+        var actualTextStyle = TextStyle.Default
+        var expectedTextStyle = TextStyle.Default
+
+        rule.setContentWithTheme {
+            expectedTextStyle = MaterialTheme.typography.title3
+            AlertWithMaterialSlc(
+                title = { actualTextStyle = LocalTextStyle.current },
+                message = {},
+                content = {},
+            )
+        }
+
+        Assert.assertEquals(expectedTextStyle, actualTextStyle)
+    }
+
+    @Test
+    fun gives_body_correct_textstyle_on_alert_for_buttons() {
+        var actualTextStyle = TextStyle.Default
+        var expectedTextStyle = TextStyle.Default
+
+        rule.setContentWithTheme {
+            expectedTextStyle = MaterialTheme.typography.body2
+            AlertWithMaterialSlc(
+                title = { Text("Title") },
+                negativeButton = {},
+                positiveButton = {},
+                content = { actualTextStyle = LocalTextStyle.current }
+            )
+        }
+
+        Assert.assertEquals(expectedTextStyle, actualTextStyle)
+    }
+
+    @Test
+    fun gives_body_correct_textstyle_on_alert_for_chips() {
+        var actualTextStyle = TextStyle.Default
+        var expectedTextStyle = TextStyle.Default
+
+        rule.setContentWithTheme {
+            expectedTextStyle = MaterialTheme.typography.body2
+            AlertWithMaterialSlc(
+                title = { Text("Title") },
+                message = { actualTextStyle = LocalTextStyle.current },
+                content = {},
+            )
+        }
+
+        Assert.assertEquals(expectedTextStyle, actualTextStyle)
+    }
+
+    @Test
+    fun gives_title_correct_textstyle_on_ConfirmationWithMaterialSlc() {
+        var actualTextStyle = TextStyle.Default
+        var expectedTextStyle = TextStyle.Default
+
+        rule.setContentWithTheme {
+            expectedTextStyle = MaterialTheme.typography.title3
+            ConfirmationWithMaterialSlc(
+                onTimeout = {},
+                content = { actualTextStyle = LocalTextStyle.current },
+            )
+        }
+
+        Assert.assertEquals(expectedTextStyle, actualTextStyle)
+    }
+}
diff --git a/wear/compose/compose-material/src/androidMain/kotlin/androidx/wear/compose/material/dialog/Dialog.android.kt b/wear/compose/compose-material/src/androidMain/kotlin/androidx/wear/compose/material/dialog/Dialog.android.kt
index 3ee68c0b..adcec0e 100644
--- a/wear/compose/compose-material/src/androidMain/kotlin/androidx/wear/compose/material/dialog/Dialog.android.kt
+++ b/wear/compose/compose-material/src/androidMain/kotlin/androidx/wear/compose/material/dialog/Dialog.android.kt
@@ -38,6 +38,8 @@
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.window.Dialog
 import androidx.compose.ui.window.DialogProperties
+import androidx.wear.compose.foundation.lazy.ScalingLazyListState
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
 import androidx.wear.compose.material.CASUAL
 import androidx.wear.compose.material.MaterialTheme
 import androidx.wear.compose.material.PositionIndicator
@@ -46,11 +48,9 @@
 import androidx.wear.compose.material.STANDARD_IN
 import androidx.wear.compose.material.STANDARD_OUT
 import androidx.wear.compose.material.Scaffold
-import androidx.wear.compose.material.ScalingLazyListState
 import androidx.wear.compose.material.SwipeToDismissBox
 import androidx.wear.compose.material.Vignette
 import androidx.wear.compose.material.VignettePosition
-import androidx.wear.compose.material.rememberScalingLazyListState
 import androidx.wear.compose.material.rememberSwipeToDismissBoxState
 
 /**
@@ -87,6 +87,80 @@
     properties: DialogProperties = DialogProperties(),
     content: @Composable () -> Unit,
 ) {
+    Dialog(
+        showDialog = showDialog,
+        onDismissRequest = onDismissRequest,
+        modifier = modifier,
+        properties = properties,
+        positionIndicator = { if (scrollState != null) PositionIndicator(scrollState) },
+        content = content
+    )
+}
+
+/**
+ * [Dialog] displays a full-screen dialog, layered over any other content. It takes a single slot,
+ * which is expected to be an opinionated Wear dialog content, such as [Alert]
+ * or [Confirmation].
+ *
+ * The dialog supports swipe-to-dismiss and reveals the parent content in the background
+ * during the swipe gesture.
+ *
+ * Example of content using [Dialog] to trigger an alert dialog using [Alert]:
+ * @sample androidx.wear.compose.material.samples.AlertDialogSample
+ *
+ * Example of content using [Dialog] to trigger a confirmation dialog using
+ * [Confirmation]:
+ * @sample androidx.wear.compose.material.samples.ConfirmationDialogSample
+
+ * @param showDialog Controls whether to display the [Dialog]. Set to true initially to trigger
+ * an 'intro' animation and display the [Dialog]. Subsequently, setting to false triggers
+ * an 'outro' animation, then [Dialog] calls [onDismissRequest] and hides itself.
+ * @param onDismissRequest Executes when the user dismisses the dialog.
+ * Must remove the dialog from the composition.
+ * @param modifier Modifier to be applied to the dialog.
+ * @param scrollState The scroll state for the dialog so that the scroll position can be displayed.
+ * @param properties Typically platform specific properties to further configure the dialog.
+ * @param content Slot for dialog content such as [Alert] or [Confirmation].
+ */
+@Suppress("DEPRECATION")
+@Deprecated(
+    "This overload is provided for backwards compatibility with Compose for Wear OS 1.1." +
+        "A newer overload is available which uses ScalingLazyListState from " +
+        "wear.compose.foundation.lazy package", level = DeprecationLevel.HIDDEN
+)
+@Composable
+public fun Dialog(
+    showDialog: Boolean,
+    onDismissRequest: () -> Unit,
+    modifier: Modifier = Modifier,
+    scrollState: androidx.wear.compose.material.ScalingLazyListState? =
+        androidx.wear.compose.material.rememberScalingLazyListState(),
+    properties: DialogProperties = DialogProperties(),
+    content: @Composable () -> Unit,
+) {
+    Dialog(
+        showDialog = showDialog,
+        onDismissRequest = onDismissRequest,
+        modifier = modifier,
+        properties = properties,
+        positionIndicator = { if (scrollState != null) PositionIndicator(scrollState) },
+        content = content
+    )
+}
+
+/**
+ * A Dialog composable which was created for sharing code between 2 versions
+ * of public [Dialog]s - with ScalingLazyListState from material and another from foundation.lazy
+ */
+@Composable
+private fun Dialog(
+    showDialog: Boolean,
+    onDismissRequest: () -> Unit,
+    modifier: Modifier = Modifier,
+    properties: DialogProperties = DialogProperties(),
+    positionIndicator: @Composable () -> Unit,
+    content: @Composable () -> Unit,
+) {
     // Transitions for background and 'dialog content' alpha.
     var alphaTransitionState by remember {
         mutableStateOf(MutableTransitionState(AlphaStage.IntroFadeOut))
@@ -101,7 +175,8 @@
 
     if (showDialog ||
         alphaTransitionState.targetState != AlphaStage.IntroFadeOut ||
-        scaleTransitionState.targetState != ScaleStage.Intro) {
+        scaleTransitionState.targetState != ScaleStage.Intro
+    ) {
         Dialog(
             onDismissRequest = onDismissRequest,
             properties = properties,
@@ -114,17 +189,19 @@
                 vignette = {
                     AnimatedVisibility(
                         visible = scaleTransitionState.targetState == ScaleStage.Display,
-                        enter = fadeIn(animationSpec =
+                        enter = fadeIn(
+                            animationSpec =
                             TweenSpec(durationMillis = CASUAL, easing = STANDARD_IN)
                         ),
-                        exit = fadeOut(animationSpec =
+                        exit = fadeOut(
+                            animationSpec =
                             TweenSpec(durationMillis = CASUAL, easing = STANDARD_OUT)
                         ),
                     ) {
                         Vignette(vignettePosition = VignettePosition.TopAndBottom)
                     }
                 },
-                positionIndicator = { if (scrollState != null) PositionIndicator(scrollState) },
+                positionIndicator = positionIndicator,
                 modifier = modifier,
             ) {
                 SwipeToDismissBox(
@@ -142,8 +219,11 @@
                     }
                 ) { isBackground ->
                     Box(
-                        modifier = Modifier.matchParentSize().background(
-                            MaterialTheme.colors.background.copy(alpha = backgroundAlpha))
+                        modifier = Modifier
+                            .matchParentSize()
+                            .background(
+                                MaterialTheme.colors.background.copy(alpha = backgroundAlpha)
+                            )
                     )
                     if (!isBackground) content()
                 }
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Picker.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Picker.kt
index 6bc2957..31db6b9 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Picker.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Picker.kt
@@ -1,4 +1,3 @@
-
 /*
  * Copyright 2021 The Android Open Source Project
  *
@@ -16,6 +15,11 @@
  */
 package androidx.wear.compose.material
 
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn as ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.ScalingParams as ScalingParams
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumnDefaults as ScalingLazyColumnDefaults
+import androidx.wear.compose.foundation.lazy.AutoCenteringParams as AutoCenteringParams
+import androidx.wear.compose.foundation.lazy.ScalingLazyListState as ScalingLazyListState
 import androidx.compose.animation.core.CubicBezierEasing
 import androidx.compose.animation.core.DecayAnimationSpec
 import androidx.compose.animation.core.Easing
@@ -113,7 +117,7 @@
     readOnly: Boolean = false,
     readOnlyLabel: @Composable (BoxScope.() -> Unit)? = null,
     onSelected: () -> Unit = {},
-    scalingParams: ScalingParams = PickerDefaults.scalingParams(),
+    scalingParams: ScalingParams = PickerDefaults.defaultScalingParams(),
     separation: Dp = 0.dp,
     /* @FloatRange(from = 0.0, to = 0.5) */
     gradientRatio: Float = PickerDefaults.DefaultGradientRatio,
@@ -224,6 +228,44 @@
     }
 }
 
+@Suppress("DEPRECATION")
+@Deprecated(
+    "This overload is provided for backwards compatibility with Compose for Wear OS 1.1." +
+        "A newer overload is available which uses ScalingParams from " +
+        "androidx.wear.compose.foundation.lazy package", level = DeprecationLevel.HIDDEN
+)
+@Composable
+public fun Picker(
+    state: PickerState,
+    contentDescription: String?,
+    modifier: Modifier = Modifier,
+    readOnly: Boolean = false,
+    readOnlyLabel: @Composable (BoxScope.() -> Unit)? = null,
+    onSelected: () -> Unit = {},
+    scalingParams: androidx.wear.compose.material.ScalingParams = PickerDefaults.scalingParams(),
+    separation: Dp = 0.dp,
+    /* @FloatRange(from = 0.0, to = 0.5) */
+    gradientRatio: Float = PickerDefaults.DefaultGradientRatio,
+    gradientColor: Color = MaterialTheme.colors.background,
+    flingBehavior: FlingBehavior = PickerDefaults.flingBehavior(state),
+    userScrollEnabled: Boolean = true,
+    option: @Composable PickerScope.(optionIndex: Int) -> Unit
+) = Picker(
+    state = state,
+    contentDescription = contentDescription,
+    modifier = modifier,
+    readOnly = readOnly,
+    readOnlyLabel = readOnlyLabel,
+    onSelected = onSelected,
+    scalingParams = convertToDefaultFoundationScalingParams(scalingParams),
+    separation = separation,
+    gradientRatio = gradientRatio,
+    gradientColor = gradientColor,
+    flingBehavior = flingBehavior,
+    userScrollEnabled = userScrollEnabled,
+    option = option
+)
+
 /**
  * A scrollable list of items to pick from. By default, items will be repeated
  * "infinitely" in both directions, unless [PickerState#repeatItems] is specified as false.
@@ -265,6 +307,7 @@
  * use on a screen, it is recommended that this content is given [Alignment.Center] in order to
  * align with the centrally selected Picker value.
  */
+@Suppress("DEPRECATION")
 @Deprecated("This overload is provided for backwards compatibility with Compose for Wear OS 1.1." +
     "A newer overload is available with additional userScrollEnabled parameter which improves " +
     "accessibility of [Picker].", level = DeprecationLevel.HIDDEN)
@@ -276,7 +319,7 @@
     readOnly: Boolean = false,
     readOnlyLabel: @Composable (BoxScope.() -> Unit)? = null,
     onSelected: () -> Unit = {},
-    scalingParams: ScalingParams = PickerDefaults.scalingParams(),
+    scalingParams: androidx.wear.compose.material.ScalingParams = PickerDefaults.scalingParams(),
     separation: Dp = 0.dp,
     /* @FloatRange(from = 0.0, to = 0.5) */
     gradientRatio: Float = PickerDefaults.DefaultGradientRatio,
@@ -290,7 +333,7 @@
     readOnly = readOnly,
     readOnlyLabel = readOnlyLabel,
     onSelected = onSelected,
-    scalingParams = scalingParams,
+    scalingParams = convertToDefaultFoundationScalingParams(scalingParams),
     separation = separation,
     gradientRatio = gradientRatio,
     gradientColor = gradientColor,
@@ -333,6 +376,7 @@
  * use on a screen, it is recommended that this content is given [Alignment.Center] in order to
  * align with the centrally selected Picker value.
  */
+@Suppress("DEPRECATION")
 @Deprecated("This overload is provided for backwards compatibility with Compose for Wear OS 1.0." +
   "A newer overload is available with additional contentDescription, onSelected and " +
   "userScrollEnabled parameters, which improves accessibility of [Picker].")
@@ -342,7 +386,7 @@
     modifier: Modifier = Modifier,
     readOnly: Boolean = false,
     readOnlyLabel: @Composable (BoxScope.() -> Unit)? = null,
-    scalingParams: ScalingParams = PickerDefaults.scalingParams(),
+    scalingParams: androidx.wear.compose.material.ScalingParams = PickerDefaults.scalingParams(),
     separation: Dp = 0.dp,
     /* @FloatRange(from = 0.0, to = 0.5) */
     gradientRatio: Float = PickerDefaults.DefaultGradientRatio,
@@ -355,7 +399,7 @@
     modifier = modifier,
     readOnly = readOnly,
     readOnlyLabel = readOnlyLabel,
-    scalingParams = scalingParams,
+    scalingParams = convertToDefaultFoundationScalingParams(scalingParams),
     separation = separation,
     gradientRatio = gradientRatio,
     gradientColor = gradientColor,
@@ -557,14 +601,27 @@
         }
     }
 }
+
 /**
  * Contains the default values used by [Picker]
  */
 public object PickerDefaults {
+
     /**
      * Scaling params are used to determine when items start to be scaled down and alpha applied,
      * and how much. For details, see [ScalingParams]
      */
+    @Suppress("DEPRECATION")
+    @Deprecated(
+        "This overload is provided for backwards compatibility with Compose for" +
+            " Wear OS 1.1 and was deprecated. Use [defaultScalingParams] instead",
+        replaceWith = ReplaceWith(
+            "PickerDefaults.defaultScalingParams(edgeScale," +
+                " edgeAlpha, minElementHeight, maxElementHeight, minTransitionArea, " +
+                "maxTransitionArea, scaleInterpolator, viewportVerticalOffsetResolver)"
+        ),
+        level = DeprecationLevel.WARNING
+    )
     public fun scalingParams(
         edgeScale: Float = 0.45f,
         edgeAlpha: Float = 1.0f,
@@ -574,16 +631,42 @@
         maxTransitionArea: Float = 0.45f,
         scaleInterpolator: Easing = CubicBezierEasing(0.25f, 0.00f, 0.75f, 1.00f),
         viewportVerticalOffsetResolver: (Constraints) -> Int = { (it.maxHeight / 5f).toInt() }
-    ): ScalingParams = DefaultScalingParams(
-        edgeScale = edgeScale,
-        edgeAlpha = edgeAlpha,
-        minElementHeight = minElementHeight,
-        maxElementHeight = maxElementHeight,
-        minTransitionArea = minTransitionArea,
-        maxTransitionArea = maxTransitionArea,
-        scaleInterpolator = scaleInterpolator,
-        viewportVerticalOffsetResolver = viewportVerticalOffsetResolver
-    )
+    ): androidx.wear.compose.material.ScalingParams =
+        androidx.wear.compose.material.ScalingLazyColumnDefaults.scalingParams(
+            edgeScale = edgeScale,
+            edgeAlpha = edgeAlpha,
+            minElementHeight = minElementHeight,
+            maxElementHeight = maxElementHeight,
+            minTransitionArea = minTransitionArea,
+            maxTransitionArea = maxTransitionArea,
+            scaleInterpolator = scaleInterpolator,
+            viewportVerticalOffsetResolver = viewportVerticalOffsetResolver
+        )
+
+    /**
+     * Scaling params are used to determine when items start to be scaled down and alpha applied,
+     * and how much. For details, see [ScalingParams]
+     */
+    public fun defaultScalingParams(
+        edgeScale: Float = 0.45f,
+        edgeAlpha: Float = 1.0f,
+        minElementHeight: Float = 0.0f,
+        maxElementHeight: Float = 0.0f,
+        minTransitionArea: Float = 0.45f,
+        maxTransitionArea: Float = 0.45f,
+        scaleInterpolator: Easing = CubicBezierEasing(0.25f, 0.00f, 0.75f, 1.00f),
+        viewportVerticalOffsetResolver: (Constraints) -> Int = { (it.maxHeight / 5f).toInt() }
+    ): ScalingParams =
+        ScalingLazyColumnDefaults.scalingParams(
+            edgeScale = edgeScale,
+            edgeAlpha = edgeAlpha,
+            minElementHeight = minElementHeight,
+            maxElementHeight = maxElementHeight,
+            minTransitionArea = minTransitionArea,
+            maxTransitionArea = maxTransitionArea,
+            scaleInterpolator = scaleInterpolator,
+            viewportVerticalOffsetResolver = viewportVerticalOffsetResolver
+        )
 
     /**
      * Create and remember a [FlingBehavior] that will represent natural fling curve with snap to
@@ -597,14 +680,13 @@
         state: PickerState,
         decay: DecayAnimationSpec<Float> = exponentialDecay()
     ): FlingBehavior {
-        return remember(state, decay) {
-            ScalingLazyColumnSnapFlingBehavior(
-                state = state.scalingLazyListState,
-                snapOffset = 0,
-                decay = decay
-            )
-        }
+        return ScalingLazyColumnDefaults.snapFlingBehavior(
+            state = state.scalingLazyListState,
+            snapOffset = 0.dp,
+            decay = decay
+        )
     }
+
     /**
      * Default Picker gradient ratio - the proportion of the Picker height allocated to each of the
      * of the top and bottom gradients.
@@ -624,6 +706,22 @@
 
 private fun positiveModule(n: Int, mod: Int) = ((n % mod) + mod) % mod
 
+private fun convertToDefaultFoundationScalingParams(
+    @Suppress("DEPRECATION")
+    scalingParams: androidx.wear.compose.material.ScalingParams
+): ScalingParams = PickerDefaults.defaultScalingParams(
+    edgeScale = scalingParams.edgeScale,
+    edgeAlpha = scalingParams.edgeAlpha,
+    minElementHeight = scalingParams.minElementHeight,
+    maxElementHeight = scalingParams.maxElementHeight,
+    minTransitionArea = scalingParams.minTransitionArea,
+    maxTransitionArea = scalingParams.maxTransitionArea,
+    scaleInterpolator = scalingParams.scaleInterpolator,
+    viewportVerticalOffsetResolver = { viewportConstraints ->
+        scalingParams.resolveViewportVerticalOffset(viewportConstraints)
+    }
+)
+
 @Stable
 private class PickerScopeImpl(
     private val pickerState: PickerState
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/PositionIndicator.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/PositionIndicator.kt
index ecd6bde..184e824 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/PositionIndicator.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/PositionIndicator.kt
@@ -16,6 +16,10 @@
 
 package androidx.wear.compose.material
 
+import androidx.wear.compose.foundation.lazy.ScalingLazyListState as ScalingLazyListState
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn as ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.ScalingLazyListItemInfo as ScalingLazyListItemInfo
+import androidx.wear.compose.foundation.lazy.ScalingLazyListAnchorType as ScalingLazyListAnchorType
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationSpec
@@ -204,6 +208,39 @@
 )
 
 /**
+ * Creates an [PositionIndicator] based on the values in a [ScalingLazyListState] object that
+ * a [ScalingLazyColumn] uses.
+ *
+ * For more information, see the
+ * [Scroll indicators](https://developer.android.com/training/wearables/components/scroll)
+ * guide.
+ *
+ * @param scalingLazyListState the [ScalingLazyListState] to use as the basis for the
+ * PositionIndicatorState.
+ * @param modifier The modifier to be applied to the component
+ * @param reverseDirection Reverses direction of PositionIndicator if true
+ */
+@Suppress("DEPRECATION")
+@Deprecated("This overload is provided for backwards compatibility with Compose for Wear OS 1.1." +
+        "A newer overload is available which uses ScalingLazyListState from " +
+        "androidx.wear.compose.foundation.lazy package", level = DeprecationLevel.WARNING)
+@Composable
+public fun PositionIndicator(
+    scalingLazyListState: androidx.wear.compose.material.ScalingLazyListState,
+    modifier: Modifier = Modifier,
+    reverseDirection: Boolean = false
+) = PositionIndicator(
+    state = MaterialScalingLazyColumnStateAdapter(
+        state = scalingLazyListState
+    ),
+    indicatorHeight = 50.dp,
+    indicatorWidth = 4.dp,
+    paddingHorizontal = 5.dp,
+    modifier = modifier,
+    reverseDirection = reverseDirection
+)
+
+/**
  * Creates an [PositionIndicator] based on the values in a [LazyListState] object that
  * a [LazyColumn] uses.
  *
@@ -654,51 +691,13 @@
  */
 internal class ScalingLazyColumnStateAdapter(
     private val state: ScalingLazyListState
-) : PositionIndicatorState {
-    override val positionFraction: Float
-        get() {
-            return if (state.layoutInfo.visibleItemsInfo.isEmpty()) {
-                0.0f
-            } else {
-                val decimalFirstItemIndex = decimalFirstItemIndex()
-                val decimalLastItemIndex = decimalLastItemIndex()
-                val decimalLastItemIndexDistanceFromEnd = state.layoutInfo.totalItemsCount -
-                    decimalLastItemIndex
+) : BaseScalingLazyColumnStateAdapter() {
 
-                if (decimalFirstItemIndex + decimalLastItemIndexDistanceFromEnd == 0.0f) {
-                    0.0f
-                } else {
-                    decimalFirstItemIndex /
-                        (decimalFirstItemIndex + decimalLastItemIndexDistanceFromEnd)
-                }
-            }
-        }
+    override fun noVisibleItems(): Boolean = state.layoutInfo.visibleItemsInfo.isEmpty()
 
-    override fun sizeFraction(scrollableContainerSizePx: Float) =
-        if (state.layoutInfo.totalItemsCount == 0) {
-            1.0f
-        } else {
-            val decimalFirstItemIndex = decimalFirstItemIndex()
-            val decimalLastItemIndex = decimalLastItemIndex()
+    override fun totalItemsCount(): Int = state.layoutInfo.totalItemsCount
 
-            (decimalLastItemIndex - decimalFirstItemIndex) /
-                state.layoutInfo.totalItemsCount.toFloat()
-        }
-
-    override fun visibility(scrollableContainerSizePx: Float): PositionIndicatorVisibility {
-        val canScroll = state.layoutInfo.visibleItemsInfo.isNotEmpty() &&
-            (decimalFirstItemIndex() > 0 ||
-                decimalLastItemIndex() < state.layoutInfo.totalItemsCount)
-        return if (canScroll) {
-            if (state.isScrollInProgress) {
-                PositionIndicatorVisibility.Show
-            } else {
-                PositionIndicatorVisibility.AutoHide
-            }
-        } else {
-            PositionIndicatorVisibility.Hide
-        }
-    }
+    override fun isScrollInProgress(): Boolean = state.isScrollInProgress
 
     override fun hashCode(): Int {
         return state.hashCode()
@@ -717,7 +716,91 @@
      * Note that decimal index calculations ignore spacing between list items both for determining
      * the number and the number of visible items.
      */
-    private fun decimalLastItemIndex(): Float {
+    override fun decimalLastItemIndex(): Float {
+        if (state.layoutInfo.visibleItemsInfo.isEmpty()) return 0f
+        val lastItem = state.layoutInfo.visibleItemsInfo.last()
+        // This is the offset of the last item w.r.t. the ScalingLazyColumn coordinate system where
+        // 0 in the center of the visible viewport and +/-(state.viewportHeightPx / 2f) are the
+        // start and end of the viewport.
+        //
+        // Note that [ScalingLazyListAnchorType] determines how the list items are anchored to the
+        // center of the viewport, it does not change viewport coordinates. As a result this
+        // calculation needs to take the anchorType into account to calculate the correct end
+        // of list item offset.
+        val lastItemEndOffset = lastItem.startOffset(state.layoutInfo.anchorType) + lastItem.size
+        val viewportEndOffset = state.layoutInfo.viewportSize.height / 2f
+        // Coerce item size to at least 1 to avoid divide by zero for zero height items
+        val lastItemVisibleFraction =
+            (1f - ((lastItemEndOffset - viewportEndOffset) /
+                lastItem.size.coerceAtLeast(1))).coerceAtMost(1f)
+
+        return lastItem.index.toFloat() + lastItemVisibleFraction
+    }
+
+    /**
+     * Provide a float value that represents the index of first visible list item in a scaling lazy
+     * column. The value should be in the range from [n,n+1] for a given index n, where n is the
+     * index of the first visible item and a value of n represents that all of the item is visible
+     * in the viewport and a value of n+1 means that only the very end|bottom of the list item is
+     * visible at the start|top of the viewport.
+     *
+     * Note that decimal index calculations ignore spacing between list items both for determining
+     * the number and the number of visible items.
+     */
+    override fun decimalFirstItemIndex(): Float {
+        if (state.layoutInfo.visibleItemsInfo.isEmpty()) return 0f
+        val firstItem = state.layoutInfo.visibleItemsInfo.first()
+        val firstItemStartOffset = firstItem.startOffset(state.layoutInfo.anchorType)
+        val viewportStartOffset = - (state.layoutInfo.viewportSize.height / 2f)
+        // Coerce item size to at least 1 to avoid divide by zero for zero height items
+        val firstItemInvisibleFraction =
+            ((viewportStartOffset - firstItemStartOffset) /
+                firstItem.size.coerceAtLeast(1)).coerceAtLeast(0f)
+
+        return firstItem.index.toFloat() + firstItemInvisibleFraction
+    }
+}
+
+/**
+ * An implementation of [PositionIndicatorState] to display the amount and position of a
+ * [ScalingLazyColumn] component via its [ScalingLazyListState].
+ *
+ * Note that size and position calculations ignore spacing between list items both for determining
+ * the number and the number of visible items.
+
+ * @param state the [ScalingLazyListState] to adapt.
+ */
+@Deprecated("Use [ScalingLazyColumnStateAdapter] instead")
+internal class MaterialScalingLazyColumnStateAdapter(
+    @Suppress("DEPRECATION")
+    private val state: androidx.wear.compose.material.ScalingLazyListState
+) : BaseScalingLazyColumnStateAdapter() {
+
+    override fun noVisibleItems(): Boolean = state.layoutInfo.visibleItemsInfo.isEmpty()
+
+    override fun totalItemsCount(): Int = state.layoutInfo.totalItemsCount
+
+    override fun isScrollInProgress(): Boolean = state.isScrollInProgress
+
+    override fun hashCode(): Int {
+        return state.hashCode()
+    }
+
+    @Suppress("DEPRECATION")
+    override fun equals(other: Any?): Boolean {
+        return (other as? MaterialScalingLazyColumnStateAdapter)?.state == state
+    }
+
+    /**
+     * Provide a float value that represents the index of the last visible list item in a scaling
+     * lazy column. The value should be in the range from [n,n+1] for a given index n, where n is
+     * the index of the last visible item and a value of n represents that only the very start|top
+     * of the item is visible, and n+1 means that whole of the item is visible in the viewport.
+     *
+     * Note that decimal index calculations ignore spacing between list items both for determining
+     * the number and the number of visible items.
+     */
+    override fun decimalLastItemIndex(): Float {
         if (state.layoutInfo.visibleItemsInfo.isEmpty()) return 0f
         val lastItem = state.layoutInfo.visibleItemsInfo.last()
         // This is the offset of the last item w.r.t. the ScalingLazyColumn coordinate system where
@@ -748,7 +831,7 @@
      * Note that decimal index calculations ignore spacing between list items both for determining
      * the number and the number of visible items.
      */
-    private fun decimalFirstItemIndex(): Float {
+    override fun decimalFirstItemIndex(): Float {
         if (state.layoutInfo.visibleItemsInfo.isEmpty()) return 0f
         val firstItem = state.layoutInfo.visibleItemsInfo.first()
         val firstItemStartOffset = firstItem.startOffset(state.anchorType.value!!)
@@ -762,6 +845,63 @@
     }
 }
 
+internal abstract class BaseScalingLazyColumnStateAdapter : PositionIndicatorState {
+    override val positionFraction: Float
+        get() {
+            return if (noVisibleItems()) {
+                0.0f
+            } else {
+                val decimalFirstItemIndex = decimalFirstItemIndex()
+                val decimalLastItemIndex = decimalLastItemIndex()
+                val decimalLastItemIndexDistanceFromEnd = totalItemsCount() -
+                    decimalLastItemIndex
+
+                if (decimalFirstItemIndex + decimalLastItemIndexDistanceFromEnd == 0.0f) {
+                    0.0f
+                } else {
+                    decimalFirstItemIndex /
+                        (decimalFirstItemIndex + decimalLastItemIndexDistanceFromEnd)
+                }
+            }
+        }
+
+    override fun sizeFraction(scrollableContainerSizePx: Float) =
+        if (totalItemsCount() == 0) {
+            1.0f
+        } else {
+            val decimalFirstItemIndex = decimalFirstItemIndex()
+            val decimalLastItemIndex = decimalLastItemIndex()
+
+            (decimalLastItemIndex - decimalFirstItemIndex) /
+                totalItemsCount().toFloat()
+        }
+
+    override fun visibility(scrollableContainerSizePx: Float): PositionIndicatorVisibility {
+        val canScroll = !noVisibleItems() &&
+            (decimalFirstItemIndex() > 0 ||
+                decimalLastItemIndex() < totalItemsCount())
+        return if (canScroll) {
+            if (isScrollInProgress()) {
+                PositionIndicatorVisibility.Show
+            } else {
+                PositionIndicatorVisibility.AutoHide
+            }
+        } else {
+            PositionIndicatorVisibility.Hide
+        }
+    }
+
+    abstract fun noVisibleItems(): Boolean
+
+    abstract fun totalItemsCount(): Int
+
+    abstract fun isScrollInProgress(): Boolean
+
+    abstract fun decimalLastItemIndex(): Float
+
+    abstract fun decimalFirstItemIndex(): Float
+}
+
 /**
  * An implementation of [PositionIndicatorState] to display the amount and position of a
  * [LazyColumn] component via its [LazyListState].
@@ -972,4 +1112,14 @@
     }
 )
 
-private fun sqr(x: Float) = x * x
\ No newline at end of file
+private fun sqr(x: Float) = x * x
+
+/**
+ * Find the start offset of the list item w.r.t. the
+ */
+internal fun ScalingLazyListItemInfo.startOffset(anchorType: ScalingLazyListAnchorType) =
+    offset - if (anchorType == ScalingLazyListAnchorType.ItemCenter) {
+        (size / 2f)
+    } else {
+        0f
+    }
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyColumn.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyColumn.kt
index b6dc5d4..d9f36e0 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyColumn.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyColumn.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("DEPRECATION")
+
 package androidx.wear.compose.material
 
 import androidx.compose.animation.core.CubicBezierEasing
@@ -35,7 +37,6 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -45,20 +46,22 @@
 import androidx.compose.ui.draw.clipToBounds
 import androidx.compose.ui.graphics.TransformOrigin
 import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.layout.layout
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalInspectionMode
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.offset
+import androidx.wear.compose.foundation.lazy.CombinedPaddingValues
+import androidx.wear.compose.foundation.lazy.verticalNegativePadding
 
 /**
  * Receiver scope which is used by [ScalingLazyColumn].
  */
+@Deprecated("Was moved to androidx.wear.compose.foundation.lazy package. " +
+    "Please use it instead")
 @ScalingLazyScopeMarker
 public sealed interface ScalingLazyListScope {
     /**
@@ -105,7 +108,9 @@
  * will be kept as the first visible one.
  * @param itemContent the content displayed by a single item
  */
-inline fun <T> ScalingLazyListScope.items(
+@Deprecated("Was moved to androidx.wear.compose.foundation.lazy package. " +
+    "Please use it instead")
+public inline fun <T> ScalingLazyListScope.items(
     items: List<T>,
     noinline key: ((item: T) -> Any)? = null,
     crossinline itemContent: @Composable ScalingLazyListItemScope.(item: T) -> Unit
@@ -125,6 +130,8 @@
  * will be kept as the first visible one.
  * @param itemContent the content displayed by a single item
  */
+@Deprecated("Was moved to androidx.wear.compose.foundation.lazy package. " +
+    "Please use it instead")
 inline fun <T> ScalingLazyListScope.itemsIndexed(
     items: List<T>,
     noinline key: ((index: Int, item: T) -> Any)? = null,
@@ -145,6 +152,8 @@
  * will be kept as the first visible one.
  * @param itemContent the content displayed by a single item
  */
+@Deprecated("Was moved to androidx.wear.compose.foundation.lazy package. " +
+    "Please use it instead")
 inline fun <T> ScalingLazyListScope.items(
     items: Array<T>,
     noinline key: ((item: T) -> Any)? = null,
@@ -165,6 +174,8 @@
  * will be kept as the first visible one.
  * @param itemContent the content displayed by a single item
  */
+@Deprecated("Was moved to androidx.wear.compose.foundation.lazy package. " +
+    "Please use it instead")
 public inline fun <T> ScalingLazyListScope.itemsIndexed(
     items: Array<T>,
     noinline key: ((index: Int, item: T) -> Any)? = null,
@@ -174,6 +185,8 @@
 }
 
 @Immutable
+@Deprecated("Was moved to androidx.wear.compose.foundation.lazy package. " +
+    "Please use it instead")
 @kotlin.jvm.JvmInline
 public value class ScalingLazyListAnchorType internal constructor(internal val type: Int) {
 
@@ -242,6 +255,8 @@
  * For an example of a [ScalingLazyColumn] with an explicit itemOffset see:
  * @sample androidx.wear.compose.material.samples.ScalingLazyColumnEdgeAnchoredAndAnimatedScrollTo
  */
+@Deprecated("Was moved to androidx.wear.compose.foundation.lazy package. " +
+    "Please use it instead")
 @Immutable
 public class AutoCenteringParams(
     // @IntRange(from = 0)
@@ -318,6 +333,8 @@
  * null no automatic space will be added and instead the developer can use [contentPadding] to
  * manually arrange the items.
  */
+@Deprecated("Was moved to androidx.wear.compose.foundation.lazy package. " +
+    "Please use it instead")
 @Composable
 public fun ScalingLazyColumn(
     modifier: Modifier = Modifier,
@@ -442,6 +459,8 @@
 /**
  * Contains the default values used by [ScalingLazyColumn]
  */
+@Deprecated("Was moved to androidx.wear.compose.foundation.lazy package. " +
+    "Please use it instead")
 public object ScalingLazyColumnDefaults {
     /**
      * Creates a [ScalingParams] that represents the scaling and alpha properties for a
@@ -670,64 +689,3 @@
         itemScope.content()
     }
 }
-
-@Immutable
-private class CombinedPaddingValues(
-    @Stable
-    val contentPadding: PaddingValues,
-    @Stable
-    val extraPadding: Dp
-) : PaddingValues {
-    override fun calculateLeftPadding(layoutDirection: LayoutDirection): Dp =
-        contentPadding.calculateLeftPadding(layoutDirection)
-
-    override fun calculateTopPadding(): Dp =
-        contentPadding.calculateTopPadding() + extraPadding
-
-    override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp =
-        contentPadding.calculateRightPadding(layoutDirection)
-
-    override fun calculateBottomPadding(): Dp =
-        contentPadding.calculateBottomPadding() + extraPadding
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other == null) return false
-        if (this::class != other::class) return false
-
-        other as CombinedPaddingValues
-
-        if (contentPadding != other.contentPadding) return false
-        if (extraPadding != other.extraPadding) return false
-
-        return true
-    }
-
-    override fun hashCode(): Int {
-        var result = contentPadding.hashCode()
-        result = 31 * result + extraPadding.hashCode()
-        return result
-    }
-
-    override fun toString(): String {
-        return "CombinedPaddingValuesImpl(contentPadding=$contentPadding, " +
-            "extraPadding=$extraPadding)"
-    }
-}
-
-private fun Modifier.verticalNegativePadding(
-    extraPadding: Dp,
-) = layout { measurable, constraints ->
-    require(constraints.hasBoundedWidth)
-    require(constraints.hasBoundedHeight)
-    val topAndBottomPadding = (extraPadding * 2).roundToPx()
-    val placeable = measurable.measure(
-        constraints.copy(
-            minHeight = constraints.minHeight + topAndBottomPadding,
-            maxHeight = constraints.maxHeight + topAndBottomPadding
-        )
-    )
-
-    layout(placeable.measuredWidth, constraints.maxHeight) {
-        placeable.place(0, -extraPadding.roundToPx())
-    }
-}
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyColumnMeasure.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyColumnMeasure.kt
index 097b26c..382be50 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyColumnMeasure.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyColumnMeasure.kt
@@ -14,16 +14,19 @@
  * limitations under the License.
  */
 
+@file:Suppress("DEPRECATION")
+
 package androidx.wear.compose.material
 
 import androidx.compose.animation.core.Easing
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.lazy.LazyListItemInfo
-import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.util.lerp
+import androidx.wear.compose.foundation.lazy.ScaleAndAlpha
+import androidx.wear.compose.foundation.lazy.inverseLerp
 import kotlin.math.min
 import kotlin.math.roundToInt
 
@@ -93,6 +96,8 @@
  * point for each item.
  */
 @Stable
+@Deprecated("Was moved to androidx.wear.compose.foundation.lazy package. " +
+    "Please use it instead")
 public interface ScalingParams {
     /**
      * What fraction of the full size of the item to scale it by when most
@@ -443,17 +448,6 @@
     }
 }
 
-@Immutable
-internal data class ScaleAndAlpha(
-    val scale: Float,
-    val alpha: Float
-
-) {
-    companion object {
-        internal val noScaling = ScaleAndAlpha(1.0f, 1.0f)
-    }
-}
-
 /**
  * Calculate the offset from the viewport center line of the Start|Center of an items unadjusted
  * or scaled size. The for items with an height that is an odd number and that have
@@ -501,11 +495,3 @@
     } else {
         0f
     }
-
-/**
- * Inverse linearly interpolate, return what fraction (0f..1f) that [value] is between [start] and
- * [stop]. Returns 0f if value =< start and 1f if value >= stop.
- */
-internal fun inverseLerp(start: Float, stop: Float, value: Float): Float {
-    return ((value - start) / (stop - start)).coerceIn(0f, 1f)
-}
\ No newline at end of file
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyColumnSnapFlingBehavior.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyColumnSnapFlingBehavior.kt
index a1fdfa3..145804d 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyColumnSnapFlingBehavior.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyColumnSnapFlingBehavior.kt
@@ -31,6 +31,7 @@
 import kotlin.math.roundToInt
 import kotlin.math.sqrt
 
+@Suppress("DEPRECATION")
 internal class ScalingLazyColumnSnapFlingBehavior(
     val state: ScalingLazyListState,
     val snapOffset: Int = 0,
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyListItemInfo.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyListItemInfo.kt
index 39ab142..def6417 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyListItemInfo.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyListItemInfo.kt
@@ -20,6 +20,8 @@
  *
  * @see ScalingLazyListLayoutInfo
  */
+@Deprecated("Was moved to androidx.wear.compose.foundation.lazy package. " +
+    "Please use it instead")
 public sealed interface ScalingLazyListItemInfo {
     /**
      * The index of the item in the list.
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyListItemScope.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyListItemScope.kt
index bb00507..d346a0b 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyListItemScope.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyListItemScope.kt
@@ -13,6 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+@file:Suppress("DEPRECATION")
+
 package androidx.wear.compose.material
 
 import androidx.compose.foundation.layout.height
@@ -28,6 +30,8 @@
  * Receiver scope being used by the item content parameter of ScalingLazyColumn.
  */
 @Stable
+@Deprecated("Was moved to androidx.wear.compose.foundation.lazy package. " +
+    "Please use it instead")
 @ScalingLazyScopeMarker
 public sealed interface ScalingLazyListItemScope {
     /**
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyListLayoutInfo.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyListLayoutInfo.kt
index 498ca2b..04e1adf 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyListLayoutInfo.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyListLayoutInfo.kt
@@ -24,10 +24,13 @@
  *
  * Use [ScalingLazyListState.layoutInfo] to retrieve this
  */
+@Deprecated("Was moved to androidx.wear.compose.foundation.lazy package. " +
+    "Please use it instead")
 public sealed interface ScalingLazyListLayoutInfo {
     /**
      * The list of [ScalingLazyListItemInfo] representing all the currently visible items.
      */
+    @Suppress("DEPRECATION")
     val visibleItemsInfo: List<ScalingLazyListItemInfo>
 
     /**
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyListState.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyListState.kt
index e7e86c7..ea0ba02 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyListState.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyListState.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("DEPRECATION")
+
 package androidx.wear.compose.material
 
 import androidx.compose.foundation.MutatePriority
@@ -44,6 +46,8 @@
  * @param initialCenterItemScrollOffset the initial value for
  * [ScalingLazyListState.centerItemScrollOffset] in pixels
  */
+@Deprecated("Was moved to androidx.wear.compose.foundation.lazy package. " +
+    "Please use it instead")
 @Composable
 public fun rememberScalingLazyListState(
     initialCenterItemIndex: Int = 1,
@@ -79,6 +83,8 @@
  * [ScalingLazyColumn]. After the [ScalingLazyColumn] is initially drawn the actual values for the
  * [centerItemIndex] and [centerItemScrollOffset] can be read from the state.
  */
+@Deprecated("Was moved to androidx.wear.compose.foundation.lazy package. " +
+    "Please use it instead")
 @Stable
 class ScalingLazyListState constructor(
     private var initialCenterItemIndex: Int = 1,
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyScopeMarker.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyScopeMarker.kt
index aedeab3..aaa2487 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyScopeMarker.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScalingLazyScopeMarker.kt
@@ -19,5 +19,7 @@
 /**
  * DSL marker used to distinguish between lazy layout scope and the item scope.
  */
+@Deprecated("Was moved to androidx.wear.compose.foundation.lazy package. " +
+    "Please use it instead")
 @DslMarker
 annotation class ScalingLazyScopeMarker
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScrollAway.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScrollAway.kt
index f2150c7..b725fb2 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScrollAway.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/ScrollAway.kt
@@ -31,6 +31,8 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.lerp
+import androidx.wear.compose.foundation.lazy.ScalingLazyListState
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
 
 /**
  * Scroll an item vertically in/out of view based on a [ScrollState].
@@ -99,8 +101,39 @@
         )
     }
 
+/**
+ * Scroll an item vertically in/out of view based on a [ScalingLazyListState].
+ * Typically used to scroll a [TimeText] item out of view as the user starts to scroll
+ * a [ScalingLazyColumn] of items upwards and bring additional items into view.
+ *
+ * @param scrollState The [ScalingLazyListState] to used as the basis for the scroll-away.
+ * @param itemIndex The item for which the scroll offset will trigger scrolling away.
+ * @param offset Adjustment to the starting point for scrolling away. Positive values result in
+ * the scroll away starting later, negative values start scrolling away earlier.
+ */
+@Deprecated(
+    "This overload is provided for backwards compatibility with Compose for Wear OS 1.1." +
+        "A newer overload is available which uses ScalingLazyListState " +
+        "from wear.compose.foundation.lazy package", level = DeprecationLevel.WARNING
+)
+public fun Modifier.scrollAway(
+    @Suppress("DEPRECATION")
+    scrollState: androidx.wear.compose.material.ScalingLazyListState,
+    itemIndex: Int = 1,
+    offset: Dp = 0.dp,
+): Modifier =
+    scrollAway {
+        ScrollParams(
+            valid = itemIndex < scrollState.layoutInfo.totalItemsCount,
+            yPx = scrollState.layoutInfo.visibleItemsInfo.find { it.index == itemIndex }?.let {
+                -it.offset - offset.toPx()
+            }
+        )
+    }
+
 private fun Modifier.scrollAway(scrollFn: Density.() -> ScrollParams): Modifier =
     this.then(
+        @Suppress("ModifierInspectorInfo")
         object : LayoutModifier {
             override fun MeasureScope.measure(
                 measurable: Measurable,
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Vignette.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Vignette.kt
index f33c89e..245a5d0 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Vignette.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Vignette.kt
@@ -23,6 +23,7 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.ContentScale
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
 
 /**
  * Possible combinations for vignette state.
@@ -103,7 +104,9 @@
                 ),
                 contentScale = ContentScale.FillWidth,
                 contentDescription = null,
-                modifier = Modifier.align(Alignment.TopCenter).fillMaxWidth(),
+                modifier = Modifier
+                    .align(Alignment.TopCenter)
+                    .fillMaxWidth(),
             )
         }
         if (vignettePosition.drawBottom()) {
@@ -114,7 +117,9 @@
                 ),
                 contentScale = ContentScale.FillWidth,
                 contentDescription = null,
-                modifier = Modifier.align(Alignment.BottomCenter).fillMaxWidth(),
+                modifier = Modifier
+                    .align(Alignment.BottomCenter)
+                    .fillMaxWidth(),
             )
         }
     }
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/dialog/Dialog.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/dialog/Dialog.kt
index fac57f8..4f35115 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/dialog/Dialog.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/dialog/Dialog.kt
@@ -20,38 +20,38 @@
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.width
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.getValue
 import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
+import androidx.wear.compose.foundation.lazy.ScalingLazyListState
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
 import androidx.wear.compose.material.Button
 import androidx.wear.compose.material.Chip
-import androidx.wear.compose.material.ToggleChip
-import androidx.wear.compose.material.contentColorFor
 import androidx.wear.compose.material.Icon
-import androidx.wear.compose.material.isRoundDevice
 import androidx.wear.compose.material.LocalContentColor
 import androidx.wear.compose.material.LocalTextStyle
 import androidx.wear.compose.material.MaterialTheme
 import androidx.wear.compose.material.PositionIndicator
 import androidx.wear.compose.material.Scaffold
-import androidx.wear.compose.material.ScalingLazyColumn
-import androidx.wear.compose.material.ScalingLazyListScope
-import androidx.wear.compose.material.ScalingLazyListState
-import androidx.wear.compose.material.rememberScalingLazyListState
+import androidx.wear.compose.material.ToggleChip
+import androidx.wear.compose.material.contentColorFor
+import androidx.wear.compose.material.isRoundDevice
 import kotlinx.coroutines.delay
 
 /**
@@ -142,6 +142,138 @@
 
 /**
  * [Alert] lays out the content for an opinionated, alert screen.
+ * This overload offers 5 slots for title, negative button, positive button, optional icon and
+ * optional content. The buttons are shown side-by-side below the icon, text and content.
+ * [Alert] is scrollable by default if the content is taller than the viewport.
+ *
+ * [Alert] can be used as a destination in a navigation graph
+ * e.g. using SwipeDismissableNavHost. However, for a conventional fullscreen dialog,
+ * displayed on top of other content, use [Dialog].
+ *
+ * Example of an [Alert] with an icon, title, body text and buttons:
+ * @sample androidx.wear.compose.material.samples.AlertWithButtons
+ *
+ * @param title A slot for displaying the title of the dialog,
+ * expected to be one or two lines of text.
+ * @param negativeButton A slot for a [Button] indicating negative sentiment (e.g. No).
+ * Clicking the button must remove the dialog from the composition hierarchy.
+ * @param positiveButton A slot for a [Button] indicating positive sentiment (e.g. Yes).
+ * Clicking the button must remove the dialog from the composition hierarchy.
+ * @param modifier Modifier to be applied to the dialog content.
+ * @param icon Optional slot for an icon to be shown at the top of the dialog.
+ * @param scrollState The scroll state for the dialog so that the scroll position can be displayed
+ * e.g. by the [PositionIndicator] passed to [Scaffold].
+ * @param backgroundColor [Color] representing the background color for the dialog.
+ * @param contentColor [Color] representing the color for [content].
+ * @param titleColor [Color] representing the color for [title].
+ * @param iconColor Icon [Color] that defaults to [contentColor],
+ * unless specifically overridden.
+ * @param verticalArrangement The vertical arrangement of the dialog's children. This allows us
+ * to add spacing between items and specify the arrangement of the items when we have not enough
+ * of them to fill the whole minimum size.
+ * @param contentPadding The padding to apply around the whole of the dialog's contents.
+ * @param content A slot for additional content, expected to be 2-3 lines of text.
+ */
+@Suppress("DEPRECATION")
+@Deprecated(
+    "This overload is provided for backwards compatibility with Compose for Wear OS 1.1." +
+        "A newer overload is available which uses ScalingLazyListState from " +
+        "wear.compose.foundation.lazy package", level = DeprecationLevel.HIDDEN
+)
+@Composable
+public fun Alert(
+    title: @Composable ColumnScope.() -> Unit,
+    negativeButton: @Composable () -> Unit,
+    positiveButton: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    icon: @Composable (ColumnScope.() -> Unit)? = null,
+    scrollState: androidx.wear.compose.material.ScalingLazyListState =
+        androidx.wear.compose.material.rememberScalingLazyListState(),
+    backgroundColor: Color = MaterialTheme.colors.background,
+    contentColor: Color = contentColorFor(backgroundColor),
+    titleColor: Color = contentColor,
+    iconColor: Color = contentColor,
+    verticalArrangement: Arrangement.Vertical = DialogDefaults.AlertVerticalArrangement,
+    contentPadding: PaddingValues = DialogDefaults.ContentPadding,
+    content: @Composable (ColumnScope.() -> Unit)? = null
+) {
+    AlertWithMaterialSlc(
+        title = title,
+        negativeButton = negativeButton,
+        positiveButton = positiveButton,
+        modifier = modifier,
+        icon = icon,
+        scrollState = scrollState,
+        backgroundColor = backgroundColor,
+        contentColor = contentColor,
+        titleColor = titleColor,
+        iconColor = iconColor,
+        verticalArrangement = verticalArrangement,
+        contentPadding = contentPadding,
+        content = content
+    )
+}
+
+/**
+ * @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ */
+@Suppress("DEPRECATION")
+@Deprecated("Used only for testing")
+@Composable
+internal fun AlertWithMaterialSlc(
+    title: @Composable ColumnScope.() -> Unit,
+    negativeButton: @Composable () -> Unit,
+    positiveButton: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    icon: @Composable (ColumnScope.() -> Unit)? = null,
+    scrollState: androidx.wear.compose.material.ScalingLazyListState =
+        androidx.wear.compose.material.rememberScalingLazyListState(),
+    backgroundColor: Color = MaterialTheme.colors.background,
+    contentColor: Color = contentColorFor(backgroundColor),
+    titleColor: Color = contentColor,
+    iconColor: Color = contentColor,
+    verticalArrangement: Arrangement.Vertical = DialogDefaults.AlertVerticalArrangement,
+    contentPadding: PaddingValues = DialogDefaults.ContentPadding,
+    content: @Composable (ColumnScope.() -> Unit)? = null
+) {
+    MaterialDialogImpl(
+        modifier = modifier,
+        scrollState = scrollState,
+        verticalArrangement = verticalArrangement,
+        contentPadding = contentPadding,
+        backgroundColor = backgroundColor,
+    ) {
+        if (icon != null) {
+            item {
+                DialogIconHeader(iconColor, content = icon)
+            }
+        }
+
+        item {
+            DialogTitle(titleColor, padding = DialogDefaults.TitlePadding, title)
+        }
+
+        if (content != null) {
+            item {
+                DialogBody(contentColor, content)
+            }
+        }
+
+        // Buttons
+        item {
+            Row(
+                verticalAlignment = Alignment.CenterVertically,
+            ) {
+                negativeButton()
+                Spacer(modifier = Modifier.width(DialogDefaults.ButtonSpacing))
+                positiveButton()
+            }
+        }
+    }
+}
+
+/**
+ * [Alert] lays out the content for an opinionated, alert screen.
  * This overload offers 4 slots for title, optional icon, optional message text and
  * a content slot expected to be one or more vertically stacked [Chip]s or [ToggleChip]s.
  * [Alert] is scrollable by default if the content is taller than the viewport.
@@ -213,6 +345,122 @@
 }
 
 /**
+ * [Alert] lays out the content for an opinionated, alert screen.
+ * This overload offers 4 slots for title, optional icon, optional message text and
+ * a content slot expected to be one or more vertically stacked [Chip]s or [ToggleChip]s.
+ * [Alert] is scrollable by default if the content is taller than the viewport.
+ *
+ * [Alert] can be used as a destination in a navigation graph
+ * e.g. using SwipeDismissableNavHost. However, for a conventional fullscreen dialog,
+ * displayed on top of other content, use [Dialog].
+ *
+ * Example of an [Alert] with an icon, title, message text and chips:
+ * @sample androidx.wear.compose.material.samples.AlertWithChips
+ *
+ * @param title A slot for displaying the title of the dialog,
+ * expected to be one or two lines of text.
+ * @param modifier Modifier to be applied to the dialog.
+ * @param icon Optional slot for an icon to be shown at the top of the dialog.
+ * @param message Optional slot for additional message content, expected to be 2-3 lines of text.
+ * @param scrollState The scroll state for the dialog so that the scroll position can be displayed
+ * e.g. by the [PositionIndicator] passed to [Scaffold].
+ * @param backgroundColor [Color] representing the background color for the dialog.
+ * @param titleColor [Color] representing the color for [title].
+ * @param messageColor [Color] representing the color for [message].
+ * @param iconColor [Color] representing the color for [icon].
+ * @param verticalArrangement The vertical arrangement of the dialog's children. This allows us
+ * to add spacing between items and specify the arrangement of the items when we have not enough
+ * of them to fill the whole minimum size.
+ * @param contentPadding The padding to apply around the whole of the dialog's contents.
+ * @param content A slot for one or more spaced [Chip]s, stacked vertically.
+ */
+@Suppress("DEPRECATION")
+@Deprecated(
+    "This overload is provided for backwards compatibility with Compose for Wear OS 1.1." +
+        "A newer overload is available which uses ScalingLazyListState and ScalingLazyListScope " +
+        "from wear.compose.foundation.lazy package", level = DeprecationLevel.HIDDEN
+)
+@Composable
+public fun Alert(
+    title: @Composable ColumnScope.() -> Unit,
+    modifier: Modifier = Modifier,
+    icon: @Composable (ColumnScope.() -> Unit)? = null,
+    message: @Composable (ColumnScope.() -> Unit)? = null,
+    scrollState: androidx.wear.compose.material.ScalingLazyListState =
+        androidx.wear.compose.material.rememberScalingLazyListState(),
+    backgroundColor: Color = MaterialTheme.colors.background,
+    titleColor: Color = contentColorFor(backgroundColor),
+    messageColor: Color = contentColorFor(backgroundColor),
+    iconColor: Color = contentColorFor(backgroundColor),
+    verticalArrangement: Arrangement.Vertical = DialogDefaults.AlertVerticalArrangement,
+    contentPadding: PaddingValues = DialogDefaults.ContentPadding,
+    content: androidx.wear.compose.material.ScalingLazyListScope.() -> Unit
+) {
+    AlertWithMaterialSlc(
+        title = title,
+        modifier = modifier,
+        icon = icon,
+        message = message,
+        scrollState = scrollState,
+        backgroundColor = backgroundColor,
+        titleColor = titleColor,
+        messageColor = messageColor,
+        iconColor = iconColor,
+        verticalArrangement = verticalArrangement,
+        contentPadding = contentPadding,
+        content = content
+    )
+}
+
+/**
+ * @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ */
+@Suppress("DEPRECATION")
+@Deprecated("Used only for testing")
+@Composable
+internal fun AlertWithMaterialSlc(
+    title: @Composable ColumnScope.() -> Unit,
+    modifier: Modifier = Modifier,
+    icon: @Composable (ColumnScope.() -> Unit)? = null,
+    message: @Composable (ColumnScope.() -> Unit)? = null,
+    scrollState: androidx.wear.compose.material.ScalingLazyListState =
+        androidx.wear.compose.material.rememberScalingLazyListState(),
+    backgroundColor: Color = MaterialTheme.colors.background,
+    titleColor: Color = contentColorFor(backgroundColor),
+    messageColor: Color = contentColorFor(backgroundColor),
+    iconColor: Color = contentColorFor(backgroundColor),
+    verticalArrangement: Arrangement.Vertical = DialogDefaults.AlertVerticalArrangement,
+    contentPadding: PaddingValues = DialogDefaults.ContentPadding,
+    content: androidx.wear.compose.material.ScalingLazyListScope.() -> Unit
+) {
+    MaterialDialogImpl(
+        modifier = modifier,
+        scrollState = scrollState,
+        verticalArrangement = verticalArrangement,
+        contentPadding = contentPadding,
+        backgroundColor = backgroundColor,
+    ) {
+        if (icon != null) {
+            item {
+                DialogIconHeader(iconColor, content = icon)
+            }
+        }
+
+        item {
+            DialogTitle(titleColor, padding = DialogDefaults.TitlePadding, content = title)
+        }
+
+        if (message != null) {
+            item {
+                DialogBody(messageColor, message)
+            }
+        }
+
+        content()
+    }
+}
+
+/**
  * [Confirmation] lays out the content for an opinionated confirmation screen that
  * displays a message to the user for [durationMillis]. It has a slot for an icon or image
  * (which could be animated).
@@ -289,6 +537,124 @@
 }
 
 /**
+ * [Confirmation] lays out the content for an opinionated confirmation screen that
+ * displays a message to the user for [durationMillis]. It has a slot for an icon or image
+ * (which could be animated).
+ *
+ * [Confirmation] can be used as a destination in a navigation graph
+ * e.g. using SwipeDismissableNavHost. However, for a conventional fullscreen dialog,
+ * displayed on top of other content, use [Dialog].
+ *
+ * Example of a [Confirmation] with animation:
+ * @sample androidx.wear.compose.material.samples.ConfirmationWithAnimation
+ *
+ * @param onTimeout Event invoked when the dialog has been shown for [durationMillis].
+ * @param modifier Modifier to be applied to the dialog.
+ * @param icon An optional slot for displaying an icon or image.
+ * @param scrollState The scroll state for the dialog so that the scroll position can be displayed
+ * e.g. by the [PositionIndicator] passed to [Scaffold].
+ * @param durationMillis The number of milliseconds for which the dialog is displayed,
+ * must be positive. Suggested values are [DialogDefaults.ShortDurationMillis],
+ * [DialogDefaults.LongDurationMillis] or [DialogDefaults.IndefiniteDurationMillis].
+ * @param backgroundColor [Color] representing the background color for this dialog.
+ * @param contentColor [Color] representing the color for [content].
+ * @param iconColor Icon [Color] that defaults to the [contentColor],
+ * unless specifically overridden.
+ * @param verticalArrangement The vertical arrangement of the dialog's children. This allows us
+ * to add spacing between items and specify the arrangement of the items when we have not enough
+ * of them to fill the whole minimum size.
+ * @param contentPadding The padding to apply around the whole of the dialog's contents.
+ * @param content A slot for the dialog title, expected to be one line of text.
+ */
+@Suppress("DEPRECATION")
+@Deprecated(
+        "This overload is provided for backwards compatibility with Compose for Wear OS 1.1." +
+        "A newer overload is available which uses ScalingLazyListState from " +
+        "wear.compose.foundation.lazy package", level = DeprecationLevel.HIDDEN
+)
+@Composable
+public fun Confirmation(
+    onTimeout: () -> Unit,
+    modifier: Modifier = Modifier,
+    icon: @Composable (ColumnScope.() -> Unit)? = null,
+    scrollState: androidx.wear.compose.material.ScalingLazyListState =
+        androidx.wear.compose.material.rememberScalingLazyListState(),
+    durationMillis: Long = DialogDefaults.ShortDurationMillis,
+    backgroundColor: Color = MaterialTheme.colors.background,
+    contentColor: Color = contentColorFor(backgroundColor),
+    iconColor: Color = contentColor,
+    verticalArrangement: Arrangement.Vertical = DialogDefaults.ConfirmationVerticalArrangement,
+    contentPadding: PaddingValues = DialogDefaults.ContentPadding,
+    content: @Composable ColumnScope.() -> Unit
+) {
+    ConfirmationWithMaterialSlc(
+        onTimeout = onTimeout,
+        modifier = modifier,
+        icon = icon,
+        scrollState = scrollState,
+        durationMillis = durationMillis,
+        backgroundColor = backgroundColor,
+        contentColor = contentColor,
+        iconColor = iconColor,
+        verticalArrangement = verticalArrangement,
+        contentPadding = contentPadding,
+        content = content
+    )
+}
+
+/**
+ * @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ */
+@Suppress("DEPRECATION")
+@Deprecated("Used only for testing")
+@Composable
+internal fun ConfirmationWithMaterialSlc(
+    onTimeout: () -> Unit,
+    modifier: Modifier = Modifier,
+    icon: @Composable (ColumnScope.() -> Unit)? = null,
+    scrollState: androidx.wear.compose.material.ScalingLazyListState =
+        androidx.wear.compose.material.rememberScalingLazyListState(),
+    durationMillis: Long = DialogDefaults.ShortDurationMillis,
+    backgroundColor: Color = MaterialTheme.colors.background,
+    contentColor: Color = contentColorFor(backgroundColor),
+    iconColor: Color = contentColor,
+    verticalArrangement: Arrangement.Vertical = DialogDefaults.ConfirmationVerticalArrangement,
+    contentPadding: PaddingValues = DialogDefaults.ContentPadding,
+    content: @Composable ColumnScope.() -> Unit
+) {
+    require(durationMillis > 0) { "Duration must be a positive integer" }
+
+    // Always refer to the latest inputs with which ConfirmationDialog was recomposed.
+    val currentOnTimeout by rememberUpdatedState(onTimeout)
+
+    LaunchedEffect(durationMillis) {
+        delay(durationMillis)
+        currentOnTimeout()
+    }
+    MaterialDialogImpl(
+        modifier = modifier,
+        scrollState = scrollState,
+        verticalArrangement = verticalArrangement,
+        contentPadding = contentPadding,
+        backgroundColor = backgroundColor,
+    ) {
+        if (icon != null) {
+            item {
+                DialogIconHeader(iconColor, content = icon)
+            }
+        }
+
+        item {
+            DialogTitle(
+                titleColor = contentColor,
+                padding = DialogDefaults.TitleBottomPadding,
+                content = content
+            )
+        }
+    }
+}
+
+/**
  * Contains the default values used by [Alert] and [Confirmation].
  */
 public object DialogDefaults {
@@ -394,6 +760,37 @@
 }
 
 /**
+ * Common Wear Material dialog implementation that offers a single content slot,
+ * fills the screen and is scrollable by default if the content is taller than the viewport.
+ */
+@Suppress("DEPRECATION")
+@Deprecated(
+    "Use [DialogImpl] targeting " +
+        "androidx.wear.compose.foundation.lazy ScalingLazyColumn instead"
+)
+@Composable
+private fun MaterialDialogImpl(
+    modifier: Modifier = Modifier,
+    scrollState: androidx.wear.compose.material.ScalingLazyListState,
+    verticalArrangement: Arrangement.Vertical,
+    backgroundColor: Color,
+    contentPadding: PaddingValues,
+    content: androidx.wear.compose.material.ScalingLazyListScope.() -> Unit
+) {
+    androidx.wear.compose.material.ScalingLazyColumn(
+        state = scrollState,
+        autoCentering = null,
+        horizontalAlignment = Alignment.CenterHorizontally,
+        verticalArrangement = verticalArrangement,
+        contentPadding = contentPadding,
+        modifier = modifier
+            .fillMaxSize()
+            .background(backgroundColor),
+        content = content
+    )
+}
+
+/**
  * [DialogIconHeader] displays an icon at the top of the dialog
  * followed by the recommended spacing.
  *
@@ -411,7 +808,11 @@
             horizontalAlignment = Alignment.CenterHorizontally
         ) {
             content()
-            Spacer(Modifier.fillMaxWidth().height(DialogDefaults.IconSpacing))
+            Spacer(
+                Modifier
+                    .fillMaxWidth()
+                    .height(DialogDefaults.IconSpacing)
+            )
         }
     }
 }
diff --git a/wear/compose/compose-navigation/samples/src/main/java/androidx/wear/compose/navigation/samples/SwipeDismissableNavHostSample.kt b/wear/compose/compose-navigation/samples/src/main/java/androidx/wear/compose/navigation/samples/SwipeDismissableNavHostSample.kt
index 32fae6d..5e7b3f48 100644
--- a/wear/compose/compose-navigation/samples/src/main/java/androidx/wear/compose/navigation/samples/SwipeDismissableNavHostSample.kt
+++ b/wear/compose/compose-navigation/samples/src/main/java/androidx/wear/compose/navigation/samples/SwipeDismissableNavHostSample.kt
@@ -29,10 +29,10 @@
 import androidx.compose.ui.unit.dp
 import androidx.navigation.NavType
 import androidx.navigation.navArgument
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
 import androidx.wear.compose.material.Button
 import androidx.wear.compose.material.CompactChip
 import androidx.wear.compose.material.ListHeader
-import androidx.wear.compose.material.ScalingLazyColumn
 import androidx.wear.compose.material.Text
 import androidx.wear.compose.navigation.SwipeDismissableNavHost
 import androidx.wear.compose.navigation.composable
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ButtonDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ButtonDemo.kt
index 891a178..98fb88f 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ButtonDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ButtonDemo.kt
@@ -35,6 +35,7 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
 import androidx.wear.compose.material.Button
 import androidx.wear.compose.material.ButtonDefaults
 import androidx.wear.compose.material.CompactButton
@@ -44,7 +45,6 @@
 import androidx.wear.compose.material.OutlinedCompactButton
 import androidx.wear.compose.material.Text
 import androidx.wear.compose.material.ToggleButton
-import androidx.wear.compose.material.rememberScalingLazyListState
 
 @Composable
 fun ButtonSizes() {
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ChipDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ChipDemo.kt
index 0276717..8835d23 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ChipDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ChipDemo.kt
@@ -48,6 +48,7 @@
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
 import androidx.wear.compose.material.Chip
 import androidx.wear.compose.material.ChipColors
 import androidx.wear.compose.material.ChipDefaults
@@ -58,7 +59,6 @@
 import androidx.wear.compose.material.MaterialTheme
 import androidx.wear.compose.material.OutlinedChip
 import androidx.wear.compose.material.OutlinedCompactChip
-import androidx.wear.compose.material.ScalingLazyColumn
 import androidx.wear.compose.material.Text
 import androidx.wear.compose.material.ToggleButton
 import androidx.wear.compose.material.ToggleChip
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
index 03a2344..c6be1c3 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DemoApp.kt
@@ -43,23 +43,23 @@
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
-import androidx.wear.compose.material.AutoCenteringParams
+import androidx.wear.compose.foundation.lazy.AutoCenteringParams
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumnDefaults
+import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
+import androidx.wear.compose.foundation.lazy.ScalingLazyListState
+import androidx.wear.compose.foundation.lazy.ScalingParams
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
 import androidx.wear.compose.material.Chip
 import androidx.wear.compose.material.ChipDefaults
 import androidx.wear.compose.material.ListHeader
 import androidx.wear.compose.material.LocalTextStyle
 import androidx.wear.compose.material.MaterialTheme
-import androidx.wear.compose.material.ScalingLazyColumn
-import androidx.wear.compose.material.ScalingLazyColumnDefaults
-import androidx.wear.compose.material.ScalingLazyListScope
-import androidx.wear.compose.material.ScalingLazyListState
-import androidx.wear.compose.material.ScalingParams
 import androidx.wear.compose.material.SwipeToDismissBox
 import androidx.wear.compose.material.SwipeToDismissBoxState
 import androidx.wear.compose.material.SwipeToDismissKeys
 import androidx.wear.compose.material.SwipeToDismissValue
 import androidx.wear.compose.material.Text
-import androidx.wear.compose.material.rememberScalingLazyListState
 import androidx.wear.compose.material.rememberSwipeToDismissBoxState
 import kotlinx.coroutines.channels.BufferOverflow
 import kotlinx.coroutines.channels.Channel
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DialogDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DialogDemo.kt
index d878605..fd110fe 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DialogDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DialogDemo.kt
@@ -35,6 +35,7 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
 import androidx.wear.compose.material.Button
 import androidx.wear.compose.material.ButtonDefaults
 import androidx.wear.compose.material.Chip
@@ -44,7 +45,6 @@
 import androidx.wear.compose.material.dialog.Alert
 import androidx.wear.compose.material.dialog.Confirmation
 import androidx.wear.compose.material.dialog.Dialog
-import androidx.wear.compose.material.rememberScalingLazyListState
 
 @Composable
 fun DialogPowerOff() {
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/FoundationDemos.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/FoundationDemos.kt
index ae0f7a3..1c592b2 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/FoundationDemos.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/FoundationDemos.kt
@@ -26,7 +26,11 @@
 import androidx.wear.compose.foundation.samples.CurvedWeight
 import androidx.wear.compose.foundation.samples.HierarchicalFocusCoordinatorSample
 import androidx.wear.compose.foundation.samples.OversizeComposable
+import androidx.wear.compose.foundation.samples.ScalingLazyColumnEdgeAnchoredAndAnimatedScrollTo
 import androidx.wear.compose.foundation.samples.SimpleCurvedWorld
+import androidx.wear.compose.foundation.samples.SimpleScalingLazyColumn
+import androidx.wear.compose.foundation.samples.SimpleScalingLazyColumnWithContentPadding
+import androidx.wear.compose.foundation.samples.SimpleScalingLazyColumnWithSnap
 
 val WearFoundationDemos = DemoCategory(
     "Foundation",
@@ -52,5 +56,35 @@
         ComposableDemo("Scrollable Row") { ScrollableRowDemo() },
         DemoCategory("Rotary Input", RotaryInputDemos),
         ComposableDemo("Focus Sample") { HierarchicalFocusCoordinatorSample() },
+        DemoCategory("Scaling Lazy Column", listOf(
+                ComposableDemo(
+                    "Defaults",
+                    "Basic ScalingLazyColumn using default values"
+                ) {
+                    SimpleScalingLazyColumn()
+                },
+                ComposableDemo(
+                    "With Content Padding",
+                    "Basic ScalingLazyColumn with autoCentering disabled and explicit " +
+                        "content padding of top = 20.dp, bottom = 20.dp"
+                ) {
+                    SimpleScalingLazyColumnWithContentPadding()
+                },
+                ComposableDemo(
+                    "With Snap",
+                    "Basic ScalingLazyColumn, center aligned with snap enabled"
+                ) {
+                    SimpleScalingLazyColumnWithSnap()
+                },
+                ComposableDemo(
+                    "Edge Anchor",
+                    "A ScalingLazyColumn with Edge (rather than center) item anchoring. " +
+                        "If you click on an item there will be an animated scroll of the " +
+                        "items edge to the center"
+                ) {
+                    ScalingLazyColumnEdgeAnchoredAndAnimatedScrollTo()
+                },
+            )
+        )
     ),
 )
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PositionIndicatorDemos.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PositionIndicatorDemos.kt
index 76fe1ef..d8f3599 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PositionIndicatorDemos.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/PositionIndicatorDemos.kt
@@ -41,6 +41,8 @@
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
 import androidx.wear.compose.material.Button
 import androidx.wear.compose.material.Chip
 import androidx.wear.compose.material.MaterialTheme
@@ -49,10 +51,8 @@
 import androidx.wear.compose.material.PositionIndicatorState
 import androidx.wear.compose.material.PositionIndicatorVisibility
 import androidx.wear.compose.material.Scaffold
-import androidx.wear.compose.material.ScalingLazyColumn
 import androidx.wear.compose.material.Text
 import androidx.wear.compose.material.ToggleButton
-import androidx.wear.compose.material.rememberScalingLazyListState
 
 @Composable
 fun HideWhenFullDemo() {
@@ -109,8 +109,8 @@
 
 @Composable
 fun ControllablePositionIndicator() {
-    var position = remember { mutableStateOf(0.2f) }
-    var size = remember { mutableStateOf(0.5f) }
+    val position = remember { mutableStateOf(0.2f) }
+    val size = remember { mutableStateOf(0.5f) }
     var alignment by remember { mutableStateOf(0) }
     var reverseDirection by remember { mutableStateOf(false) }
     var layoutDirection by remember { mutableStateOf(false) }
@@ -139,7 +139,9 @@
             }
         ) {
             Box(
-                modifier = Modifier.fillMaxHeight().padding(horizontal = 20.dp),
+                modifier = Modifier
+                    .fillMaxHeight()
+                    .padding(horizontal = 20.dp),
                 contentAlignment = Alignment.Center
             ) {
                 Column {
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ScalingLazyColumnDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ScalingLazyColumnDemo.kt
index 33dd48b..7597a57 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ScalingLazyColumnDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ScalingLazyColumnDemo.kt
@@ -28,15 +28,15 @@
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.Dp
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
 import androidx.wear.compose.material.AppCard
 import androidx.wear.compose.material.CardDefaults
 import androidx.wear.compose.material.Chip
 import androidx.wear.compose.material.ChipDefaults
 import androidx.wear.compose.material.ListHeader
 import androidx.wear.compose.material.MaterialTheme
-import androidx.wear.compose.material.ScalingLazyColumn
 import androidx.wear.compose.material.Text
-import androidx.wear.compose.material.rememberScalingLazyListState
 import kotlin.math.roundToInt
 
 @Composable
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ScrollAwayDemos.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ScrollAwayDemos.kt
index 6afd2a2..5ff8866 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ScrollAwayDemos.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ScrollAwayDemos.kt
@@ -33,7 +33,9 @@
 import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
-import androidx.wear.compose.material.AutoCenteringParams
+import androidx.wear.compose.foundation.lazy.AutoCenteringParams
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
 import androidx.wear.compose.material.Card
 import androidx.wear.compose.material.Chip
 import androidx.wear.compose.material.ChipDefaults
@@ -41,10 +43,8 @@
 import androidx.wear.compose.material.MaterialTheme
 import androidx.wear.compose.material.PositionIndicator
 import androidx.wear.compose.material.Scaffold
-import androidx.wear.compose.material.ScalingLazyColumn
 import androidx.wear.compose.material.Text
 import androidx.wear.compose.material.TimeText
-import androidx.wear.compose.material.rememberScalingLazyListState
 import androidx.wear.compose.material.scrollAway
 
 @Composable
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SliderDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SliderDemo.kt
index 7eb5827..df5330a 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SliderDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/SliderDemo.kt
@@ -31,7 +31,7 @@
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
-import androidx.wear.compose.material.AutoCenteringParams
+import androidx.wear.compose.foundation.lazy.AutoCenteringParams
 import androidx.wear.compose.material.Icon
 import androidx.wear.compose.material.InlineSlider
 import androidx.wear.compose.material.InlineSliderColors
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ToggleChipDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ToggleChipDemo.kt
index b5c6dfe..0b71905 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ToggleChipDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ToggleChipDemo.kt
@@ -35,20 +35,20 @@
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.lazy.ScalingLazyListState
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
 import androidx.wear.compose.material.Checkbox
 import androidx.wear.compose.material.CheckboxDefaults
 import androidx.wear.compose.material.ListHeader
 import androidx.wear.compose.material.MaterialTheme
 import androidx.wear.compose.material.RadioButton
 import androidx.wear.compose.material.RadioButtonDefaults
-import androidx.wear.compose.material.ScalingLazyListState
 import androidx.wear.compose.material.SplitToggleChip
 import androidx.wear.compose.material.Switch
 import androidx.wear.compose.material.SwitchDefaults
 import androidx.wear.compose.material.Text
 import androidx.wear.compose.material.ToggleChip
 import androidx.wear.compose.material.ToggleChipDefaults
-import androidx.wear.compose.material.rememberScalingLazyListState
 
 @Composable
 fun ToggleChips(
diff --git a/wear/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/compose/integration/macrobenchmark/target/ScrollActivity.kt b/wear/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/compose/integration/macrobenchmark/target/ScrollActivity.kt
index e35455c..ee57c4f 100644
--- a/wear/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/compose/integration/macrobenchmark/target/ScrollActivity.kt
+++ b/wear/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/compose/integration/macrobenchmark/target/ScrollActivity.kt
@@ -16,8 +16,8 @@
 
 package androidx.wear.compose.integration.macrobenchmark.target
 
-import androidx.activity.ComponentActivity
 import android.os.Bundle
+import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
@@ -31,10 +31,10 @@
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
 import androidx.wear.compose.material.MaterialTheme
-import androidx.wear.compose.material.ScalingLazyColumn
 import androidx.wear.compose.material.Text
-import androidx.wear.compose.material.rememberScalingLazyListState
 
 class ScrollActivity : ComponentActivity() {
     private var itemHeightDp: Dp = 20.dp
@@ -56,7 +56,8 @@
                     modifier = Modifier.semantics { contentDescription = CONTENT_DESCRIPTION }
                 ) {
                     items(5000) { it ->
-                        Box(Modifier
+                        Box(
+                            Modifier
                                 .requiredHeight(itemHeightDp)
                                 .background(MaterialTheme.colors.surface)
                                 .fillMaxSize()
diff --git a/wear/watchface/watchface-complications-data-source/src/main/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceService.kt b/wear/watchface/watchface-complications-data-source/src/main/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceService.kt
index ecc3af3..e5d3585 100644
--- a/wear/watchface/watchface-complications-data-source/src/main/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceService.kt
+++ b/wear/watchface/watchface-complications-data-source/src/main/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceService.kt
@@ -13,13 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package androidx.wear.watchface.complications.datasource
 
+import android.support.wearable.complications.ComplicationData as WireComplicationData
 import android.annotation.SuppressLint
 import android.app.Activity
 import android.app.Service
 import android.content.ComponentName
 import android.content.Intent
+import android.os.Build
 import android.os.Handler
 import android.os.IBinder
 import android.os.Looper
@@ -30,6 +33,7 @@
 import androidx.annotation.MainThread
 import androidx.annotation.RestrictTo
 import androidx.wear.watchface.complications.data.ComplicationData
+import androidx.wear.watchface.complications.data.ComplicationDataExpressionEvaluator
 import androidx.wear.watchface.complications.data.ComplicationType
 import androidx.wear.watchface.complications.data.ComplicationType.Companion.fromWireType
 import androidx.wear.watchface.complications.data.NoDataComplicationData
@@ -37,6 +41,9 @@
 import androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.Companion.METADATA_KEY_IMMEDIATE_UPDATE_PERIOD_MILLISECONDS
 import androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.ComplicationRequestListener
 import java.util.concurrent.CountDownLatch
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.android.asCoroutineDispatcher
+import kotlinx.coroutines.launch
 
 /**
  * Data associated with complication request in
@@ -191,7 +198,11 @@
  */
 public abstract class ComplicationDataSourceService : Service() {
     private var wrapper: IComplicationProviderWrapper? = null
+    private var lastExpressionEvaluator: ComplicationDataExpressionEvaluator? = null
     internal val mainThreadHandler by lazy { createMainThreadHandler() }
+    internal val mainThreadCoroutineScope by lazy {
+        CoroutineScope(mainThreadHandler.asCoroutineDispatcher())
+    }
 
     /* @hide */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -208,6 +219,11 @@
         return null
     }
 
+    override fun onDestroy() {
+        super.onDestroy()
+        lastExpressionEvaluator?.close()
+    }
+
     /**
      * Called when a complication is activated.
      *
@@ -382,11 +398,7 @@
                                 }
                             }
 
-                            // When no update is needed, the complicationData is going to be null.
-                            iComplicationManager.updateComplicationData(
-                                complicationInstanceId,
-                                complicationData?.asWireComplicationData()
-                            )
+                            complicationData?.asWireComplicationData().evaluateAndUpdateManager()
                         }
 
                         override fun onComplicationDataTimeline(
@@ -442,17 +454,54 @@
                                     }
                                 }
                             }
-                            // When no update is needed, the complicationData is going to be null.
-                            iComplicationManager.updateComplicationData(
-                                complicationInstanceId,
-                                complicationDataTimeline?.asWireComplicationData()
-                            )
+                            complicationDataTimeline?.asWireComplicationData()
+                                .evaluateAndUpdateManager()
+                        }
+
+                        private fun WireComplicationData?.evaluateAndUpdateManager() {
+                            lastExpressionEvaluator?.close() // Cancelling any previous evaluation.
+                            if (
+                            // Will be evaluated by the platform.
+                                Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU ||
+                                // When no update is needed, the data is going to be null.
+                                this == null
+                            ) {
+                                iComplicationManager.updateComplicationData(
+                                    complicationInstanceId,
+                                    this
+                                )
+                                return
+                            }
+                            lastExpressionEvaluator =
+                                ComplicationDataExpressionEvaluator(this).apply {
+                                    init()
+                                    listenAndUpdateManager(
+                                        iComplicationManager,
+                                        complicationInstanceId,
+                                    )
+                                }
                         }
                     }
                 )
             }
         }
 
+        private fun ComplicationDataExpressionEvaluator.listenAndUpdateManager(
+            iComplicationManager: IComplicationManager,
+            complicationInstanceId: Int,
+        ) {
+            mainThreadCoroutineScope.launch {
+                data.collect { evaluatedData ->
+                    if (evaluatedData == null) return@collect // Ignore pre-evaluation.
+                    close() // Doing one-off evaluation, the service will be re-invoked.
+                    iComplicationManager.updateComplicationData(
+                        complicationInstanceId,
+                        evaluatedData
+                    )
+                }
+            }
+        }
+
         @SuppressLint("SyntheticAccessor")
         override fun onComplicationDeactivated(complicationInstanceId: Int) {
             mainThreadHandler.post {
diff --git a/wear/watchface/watchface-complications-data-source/src/test/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceServiceTest.java b/wear/watchface/watchface-complications-data-source/src/test/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceServiceTest.java
index cc84470..bc848ab 100644
--- a/wear/watchface/watchface-complications-data-source/src/test/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceServiceTest.java
+++ b/wear/watchface/watchface-complications-data-source/src/test/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceServiceTest.java
@@ -18,12 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 
 import android.content.Intent;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.RemoteException;
@@ -38,9 +37,9 @@
 import androidx.wear.watchface.complications.data.ComplicationType;
 import androidx.wear.watchface.complications.data.LongTextComplicationData;
 import androidx.wear.watchface.complications.data.PlainComplicationText;
-import androidx.wear.watchface.complications.data.TimeRange;
+import androidx.wear.watchface.complications.data.StringExpression;
+import androidx.wear.watchface.complications.data.StringExpressionComplicationText;
 
-import org.jetbrains.annotations.NotNull;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -48,8 +47,9 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
 import org.robolectric.annotation.internal.DoNotInstrument;
-import org.robolectric.shadows.ShadowLooper;
+import org.robolectric.shadows.ShadowLog;
 
 import java.time.Instant;
 import java.util.ArrayList;
@@ -72,7 +72,7 @@
     private IComplicationManager mRemoteManager;
 
     private final CountDownLatch mUpdateComplicationDataLatch = new CountDownLatch(1);
-    private IComplicationManager.Stub mLocalManager = new IComplicationManager.Stub() {
+    private final IComplicationManager.Stub mLocalManager = new IComplicationManager.Stub() {
         @Override
         public void updateComplicationData(int complicationSlotId,
                 android.support.wearable.complications.ComplicationData data)
@@ -82,12 +82,42 @@
         }
     };
 
-    private IComplicationProvider.Stub mComplicationProvider;
-    private IComplicationProvider.Stub mNoUpdateComplicationProvider;
-    private IComplicationProvider.Stub mWrongComplicationProvider;
-    private IComplicationProvider.Stub mTimelineProvider;
+    private IComplicationProvider.Stub mProvider;
 
-    private ComplicationDataSourceService mTestService = new ComplicationDataSourceService() {
+    /**
+     * Mock implementation of ComplicationDataSourceService.
+     *
+     * <p>Can't use Mockito because it doesn't like partially implemented classes.
+     */
+    private class MockComplicationDataSourceService extends ComplicationDataSourceService {
+        boolean respondWithTimeline = false;
+
+        /**
+         * Will be used to invoke {@link ComplicationRequestListener#onComplicationData} on
+         * {@link #onComplicationRequest}.
+         */
+        @Nullable
+        ComplicationData responseData;
+
+        /**
+         * Will be used to invoke {@link ComplicationRequestListener#onComplicationDataTimeline} on
+         * {@link #onComplicationRequest}, if {@link #respondWithTimeline} is true.
+         */
+        @Nullable
+        ComplicationDataTimeline responseDataTimeline;
+
+        /** Last request provided to {@link #onComplicationRequest}. */
+        @Nullable
+        ComplicationRequest lastRequest;
+
+        /** Will be returned from {@link #getPreviewData}. */
+        @Nullable
+        ComplicationData previewData;
+
+        /** Last type provided to {@link #getPreviewData}. */
+        @Nullable
+        ComplicationType lastPreviewType;
+
         @NonNull
         @Override
         public Handler createMainThreadHandler() {
@@ -95,20 +125,15 @@
         }
 
         @Override
-        public void onComplicationRequest(
-                @NotNull ComplicationRequest request,
+        public void onComplicationRequest(@NonNull ComplicationRequest request,
                 @NonNull ComplicationRequestListener listener) {
+            lastRequest = request;
             try {
-                String response = request.isImmediateResponseRequired()
-                        ? "hello synchronous " + request.getComplicationInstanceId() :
-                        "hello " + request.getComplicationInstanceId();
-
-                listener.onComplicationData(
-                        new LongTextComplicationData.Builder(
-                                new PlainComplicationText.Builder(response).build(),
-                                ComplicationText.EMPTY
-                        ).build()
-                );
+                if (respondWithTimeline) {
+                    listener.onComplicationDataTimeline(responseDataTimeline);
+                } else {
+                    listener.onComplicationData(responseData);
+                }
             } catch (RemoteException e) {
                 Log.e(TAG, "onComplicationRequest failed with error: ", e);
             }
@@ -117,161 +142,21 @@
         @Nullable
         @Override
         public ComplicationData getPreviewData(@NonNull ComplicationType type) {
-            if (type == ComplicationType.PHOTO_IMAGE) {
-                return null;
-            }
-            return new LongTextComplicationData.Builder(
-                    new PlainComplicationText.Builder("hello preview").build(),
-                    ComplicationText.EMPTY
-            ).build();
+            lastPreviewType = type;
+            return previewData;
         }
-    };
+    }
 
-    private ComplicationDataSourceService mTestServiceNotValidTimeRange =
-            new ComplicationDataSourceService() {
-                @Override
-                public void onComplicationRequest(
-                        @NotNull ComplicationRequest request,
-                        @NonNull ComplicationRequestListener listener) {
-                    try {
-                        listener.onComplicationData(
-                                new LongTextComplicationData.Builder(
-                                        new PlainComplicationText.Builder(
-                                                "hello " + request.getComplicationInstanceId()
-                                        ).build(),
-                                        ComplicationText.EMPTY
-                                ).build()
-                        );
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "onComplicationRequest failed with error: ", e);
-                    }
-                }
-
-                @Nullable
-                @Override
-                public ComplicationData getPreviewData(@NonNull ComplicationType type) {
-                    return new LongTextComplicationData.Builder(
-                            new PlainComplicationText.Builder("hello preview").build(),
-                            ComplicationText.EMPTY
-                    ).setValidTimeRange(TimeRange.between(Instant.now(), Instant.now())).build();
-                }
-            };
-
-    private ComplicationDataSourceService mNoUpdateTestService =
-            new ComplicationDataSourceService() {
-                @NonNull
-                @Override
-                public Handler createMainThreadHandler() {
-                    return mPretendMainThreadHandler;
-                }
-
-                @Override
-                public void onComplicationRequest(
-                        @NotNull ComplicationRequest request,
-                        @NonNull ComplicationRequestListener listener) {
-                    try {
-                        // Null means no update required.
-                        listener.onComplicationData(null);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "onComplicationRequest failed with error: ", e);
-                    }
-                }
-
-                @Nullable
-                @Override
-                public ComplicationData getPreviewData(@NonNull ComplicationType type) {
-                    return new LongTextComplicationData.Builder(
-                            new PlainComplicationText.Builder("hello preview").build(),
-                            ComplicationText.EMPTY
-                    ).build();
-                }
-            };
-
-    private ComplicationDataSourceService mTimelineTestService =
-            new ComplicationDataSourceService() {
-                @NonNull
-                @Override
-                public Handler createMainThreadHandler() {
-                    return mPretendMainThreadHandler;
-                }
-
-                @Override
-                public void onComplicationRequest(
-                        @NotNull ComplicationRequest request,
-                        @NonNull ComplicationRequestListener listener) {
-                    try {
-                        ArrayList<TimelineEntry> timeline = new ArrayList<>();
-                        timeline.add(new TimelineEntry(
-                                        new TimeInterval(
-                                                Instant.ofEpochSecond(1000),
-                                                Instant.ofEpochSecond(4000)
-                                        ),
-                                        new LongTextComplicationData.Builder(
-                                                new PlainComplicationText.Builder(
-                                                        "A").build(),
-                                                ComplicationText.EMPTY
-                                        ).build()
-                                )
-                        );
-
-                        timeline.add(new TimelineEntry(
-                                        new TimeInterval(
-                                                Instant.ofEpochSecond(6000),
-                                                Instant.ofEpochSecond(8000)
-                                        ),
-                                        new LongTextComplicationData.Builder(
-                                                new PlainComplicationText.Builder(
-                                                        "B").build(),
-                                                ComplicationText.EMPTY
-                                        ).build()
-                                )
-                        );
-
-                        listener.onComplicationDataTimeline(
-                                new ComplicationDataTimeline(
-                                        new LongTextComplicationData.Builder(
-                                                new PlainComplicationText.Builder(
-                                                        "default").build(),
-                                                ComplicationText.EMPTY
-                                        ).build(),
-                                        timeline
-                                ));
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "onComplicationRequest failed with error: ", e);
-                    }
-                }
-
-                @Nullable
-                @Override
-                public ComplicationData getPreviewData(@NonNull ComplicationType type) {
-                    return new LongTextComplicationData.Builder(
-                            new PlainComplicationText.Builder("hello preview").build(),
-                            ComplicationText.EMPTY
-                    ).build();
-                }
-            };
+    private final MockComplicationDataSourceService mService =
+            new MockComplicationDataSourceService();
 
     @SuppressWarnings("deprecation") // b/251211092
     @Before
     public void setUp() {
+        ShadowLog.setLoggable("ComplicationData", Log.DEBUG);
         MockitoAnnotations.initMocks(this);
-        mComplicationProvider =
-                (IComplicationProvider.Stub) mTestService.onBind(
-                        new Intent(
-                                ComplicationDataSourceService.ACTION_COMPLICATION_UPDATE_REQUEST));
-
-        mNoUpdateComplicationProvider =
-                (IComplicationProvider.Stub) mNoUpdateTestService.onBind(
-                        new Intent(
-                                ComplicationDataSourceService.ACTION_COMPLICATION_UPDATE_REQUEST));
-
-        mWrongComplicationProvider =
-                (IComplicationProvider.Stub) mTestServiceNotValidTimeRange.onBind(
-                        new Intent(
-                                ComplicationDataSourceService.ACTION_COMPLICATION_UPDATE_REQUEST));
-
-        mTimelineProvider =
-                (IComplicationProvider.Stub) mTimelineTestService.onBind(
+        mProvider =
+                (IComplicationProvider.Stub) mService.onBind(
                         new Intent(
                                 ComplicationDataSourceService.ACTION_COMPLICATION_UPDATE_REQUEST));
 
@@ -280,14 +165,20 @@
     }
 
     @After
-    public void tareDown() {
+    public void tearDown() {
         mPretendMainThread.quitSafely();
     }
 
     @Test
     public void testOnComplicationRequest() throws Exception {
+        mService.responseData =
+                new LongTextComplicationData.Builder(
+                        new PlainComplicationText.Builder("hello").build(),
+                        ComplicationText.EMPTY
+                ).build();
+
         int id = 123;
-        mComplicationProvider.onUpdate(
+        mProvider.onUpdate(
                 id, ComplicationType.LONG_TEXT.toWireComplicationType(), mLocalManager);
         assertThat(mUpdateComplicationDataLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue();
 
@@ -295,13 +186,71 @@
                 ArgumentCaptor.forClass(
                         android.support.wearable.complications.ComplicationData.class);
         verify(mRemoteManager).updateComplicationData(eq(id), data.capture());
-        assertThat(data.getValue().getLongText().getTextAt(null, 0)).isEqualTo(
-                "hello " + id
-        );
+        assertThat(data.getValue().getLongText().getTextAt(null, 0)).isEqualTo("hello");
+    }
+
+    @Test
+    @Config(sdk = Build.VERSION_CODES.TIRAMISU)
+    public void testOnComplicationRequestWithExpression_doesNotEvaluateExpression()
+            throws Exception {
+        mService.responseData =
+                new LongTextComplicationData.Builder(
+                        new StringExpressionComplicationText(
+                                new StringExpression(new byte[]{1, 2})),
+                        ComplicationText.EMPTY)
+                        .build();
+
+        mProvider.onUpdate(
+                /* complicationInstanceId = */ 123,
+                ComplicationType.LONG_TEXT.toWireComplicationType(),
+                mLocalManager);
+
+        assertThat(mUpdateComplicationDataLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue();
+        verify(mRemoteManager).updateComplicationData(
+                eq(123),
+                eq(new LongTextComplicationData.Builder(
+                        new StringExpressionComplicationText(
+                                new StringExpression(new byte[]{1, 2})),
+                        ComplicationText.EMPTY)
+                        .build()
+                        .asWireComplicationData()));
+    }
+
+    @Test
+    @Config(sdk = Build.VERSION_CODES.S)
+    public void testOnComplicationRequestWithExpressionPreT_evaluatesExpression()
+            throws Exception {
+        mService.responseData =
+                new LongTextComplicationData.Builder(
+                        new StringExpressionComplicationText(
+                                new StringExpression(new byte[]{1, 2})),
+                        ComplicationText.EMPTY)
+                        .build();
+
+        mProvider.onUpdate(
+                /* complicationInstanceId = */ 123,
+                ComplicationType.LONG_TEXT.toWireComplicationType(),
+                mLocalManager);
+
+        assertThat(mUpdateComplicationDataLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue();
+        verify(mRemoteManager).updateComplicationData(
+                eq(123),
+                eq(new LongTextComplicationData.Builder(
+                        // TODO(b/260065006): Verify that it is actually evaluated.
+                        new StringExpressionComplicationText(
+                                new StringExpression(new byte[]{1, 2})),
+                        ComplicationText.EMPTY)
+                        .build()
+                        .asWireComplicationData()));
     }
 
     @Test
     public void testOnComplicationRequestWrongType() throws Exception {
+        mService.responseData =
+                new LongTextComplicationData.Builder(
+                        new PlainComplicationText.Builder("hello").build(),
+                        ComplicationText.EMPTY
+                ).build();
         int id = 123;
         AtomicReference<Throwable> exception = new AtomicReference<>();
         CountDownLatch exceptionLatch = new CountDownLatch(1);
@@ -311,7 +260,7 @@
             exceptionLatch.countDown();
         });
 
-        mComplicationProvider.onUpdate(
+        mProvider.onUpdate(
                 id, ComplicationType.SHORT_TEXT.toWireComplicationType(), mLocalManager);
 
         assertThat(exceptionLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue();
@@ -319,17 +268,11 @@
     }
 
     @Test
-    public void testOnComplicationRequestWrongValidTimeRange() throws Exception {
-        int id = 123;
-        mWrongComplicationProvider.onUpdate(
-                id, ComplicationType.SHORT_TEXT.toWireComplicationType(), mLocalManager);
-        assertThrows(IllegalArgumentException.class, ShadowLooper::runUiThreadTasks);
-    }
-
-    @Test
     public void testOnComplicationRequestNoUpdateRequired() throws Exception {
+        mService.responseData = null;
+
         int id = 123;
-        mNoUpdateComplicationProvider.onUpdate(
+        mProvider.onUpdate(
                 id, ComplicationType.LONG_TEXT.toWireComplicationType(), mLocalManager);
         assertThat(mUpdateComplicationDataLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue();
 
@@ -342,23 +285,55 @@
 
     @Test
     public void testGetComplicationPreviewData() throws Exception {
-        assertThat(mComplicationProvider.getComplicationPreviewData(
+        mService.previewData = new LongTextComplicationData.Builder(
+                new PlainComplicationText.Builder("hello preview").build(),
+                ComplicationText.EMPTY
+        ).build();
+
+        assertThat(mProvider.getComplicationPreviewData(
                 ComplicationType.LONG_TEXT.toWireComplicationType()
         ).getLongText().getTextAt(null, 0)).isEqualTo("hello preview");
     }
 
     @Test
-    public void testGetComplicationPreviewDataReturnsNull() throws Exception {
-        // The ComplicationProvider doesn't support PHOTO_IMAGE so null should be returned.
-        assertNull(mComplicationProvider.getComplicationPreviewData(
-                ComplicationType.PHOTO_IMAGE.toWireComplicationType())
-        );
-    }
-
-    @Test
     public void testTimelineTestService() throws Exception {
+        mService.respondWithTimeline = true;
+        ArrayList<TimelineEntry> timeline = new ArrayList<>();
+        timeline.add(new TimelineEntry(
+                        new TimeInterval(
+                                Instant.ofEpochSecond(1000),
+                                Instant.ofEpochSecond(4000)
+                        ),
+                        new LongTextComplicationData.Builder(
+                                new PlainComplicationText.Builder(
+                                        "A").build(),
+                                ComplicationText.EMPTY
+                        ).build()
+                )
+        );
+        timeline.add(new TimelineEntry(
+                        new TimeInterval(
+                                Instant.ofEpochSecond(6000),
+                                Instant.ofEpochSecond(8000)
+                        ),
+                        new LongTextComplicationData.Builder(
+                                new PlainComplicationText.Builder(
+                                        "B").build(),
+                                ComplicationText.EMPTY
+                        ).build()
+                )
+        );
+        mService.responseDataTimeline = new ComplicationDataTimeline(
+                new LongTextComplicationData.Builder(
+                        new PlainComplicationText.Builder(
+                                "default").build(),
+                        ComplicationText.EMPTY
+                ).build(),
+                timeline
+        );
+
         int id = 123;
-        mTimelineProvider.onUpdate(
+        mProvider.onUpdate(
                 id, ComplicationType.LONG_TEXT.toWireComplicationType(), mLocalManager);
         assertThat(mUpdateComplicationDataLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue();
 
@@ -389,6 +364,11 @@
     @Test
     public void testImmediateRequest() throws Exception {
         int id = 123;
+        mService.responseData =
+                new LongTextComplicationData.Builder(
+                        new PlainComplicationText.Builder("hello").build(),
+                        ComplicationText.EMPTY
+                ).build();
         HandlerThread thread = new HandlerThread("testThread");
 
         try {
@@ -400,7 +380,8 @@
 
             threadHandler.post(() -> {
                         try {
-                            response.set(mComplicationProvider.onSynchronousComplicationRequest(123,
+                            response.set(mProvider.onSynchronousComplicationRequest(
+                                    123,
                                     ComplicationType.LONG_TEXT.toWireComplicationType()));
                             doneLatch.countDown();
                         } catch (RemoteException e) {
@@ -410,9 +391,7 @@
             );
 
             assertThat(doneLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue();
-            assertThat(response.get().getLongText().getTextAt(null, 0)).isEqualTo(
-                    "hello synchronous " + id
-            );
+            assertThat(response.get().getLongText().getTextAt(null, 0)).isEqualTo("hello");
         } finally {
             thread.quitSafely();
         }
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/ComplicationDataExpressionEvaluator.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/ComplicationDataExpressionEvaluator.kt
index 17770c2..e7de294 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/ComplicationDataExpressionEvaluator.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/ComplicationDataExpressionEvaluator.kt
@@ -23,6 +23,7 @@
 import kotlinx.coroutines.asCoroutineDispatcher
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.launch
 
@@ -39,13 +40,13 @@
     val unevaluatedData: WireComplicationData,
 ) : AutoCloseable {
 
-    private val _data: MutableStateFlow<WireComplicationData?> = MutableStateFlow(null)
+    private val _data = MutableStateFlow<WireComplicationData?>(null)
 
     /**
      * The evaluated data, or `null` if it wasn't evaluated yet, or [NoDataComplicationData] if it
      * wasn't possible to evaluate the [unevaluatedData].
      */
-    val data = _data.asStateFlow()
+    val data: StateFlow<WireComplicationData?> = _data.asStateFlow()
 
     @GuardedBy("listeners")
     private val listeners = mutableMapOf<Listener, CoroutineScope>()
diff --git a/wear/watchface/watchface-style/build.gradle b/wear/watchface/watchface-style/build.gradle
index 8f3b413..8bb7927 100644
--- a/wear/watchface/watchface-style/build.gradle
+++ b/wear/watchface/watchface-style/build.gradle
@@ -23,6 +23,34 @@
     id("kotlin-android")
 }
 
+// This task copies the apks provided by the `apkAssets` configuration and places them in the
+// assets folder. This allows a build time generation of the sample apps.
+def copyApkTaskProvider = tasks.register("copyApkAssets", Copy) {
+    description = "Copies the asset apks provided by testfixture projects"
+    dependsOn(configurations.getByName("apkAssets"))
+    from(configurations.getByName("apkAssets").incoming.artifactView {}.files)
+    into(layout.buildDirectory.dir("intermediates/apkAssets"))
+
+    // Note that the artifact directory included contain multiple output-metadata.json files built
+    // with the apks. Since we're not interested in those we can simply exclude duplicates.
+    duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
+}
+
+// Define a configuration that can be resolved. This project is the consumer of test apks, i.e. it
+// contains the integration tests.
+configurations {
+    apkAssets {
+        canBeConsumed = false
+        canBeResolved = true
+        attributes {
+            attribute(
+                    LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE,
+                    objects.named(LibraryElements, 'apkAssets')
+            )
+        }
+    }
+}
+
 dependencies {
     api("androidx.annotation:annotation:1.1.0")
     api(project(":wear:watchface:watchface-complications"))
@@ -45,6 +73,14 @@
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.testRules)
     androidTestImplementation(libs.truth)
+    androidTestApi(project(":wear:watchface:watchface-style-old-api-test-stub"))
+
+    apkAssets(project(":wear:watchface:watchface-style-old-api-test-service"))
+}
+
+// It makes sure that the apks are generated before the assets are packed.
+afterEvaluate {
+    tasks.named("generateDebugAndroidTestAssets").configure { it.dependsOn(copyApkTaskProvider) }
 }
 
 android {
@@ -54,6 +90,7 @@
 
     // Use Robolectric 4.+
     testOptions.unitTests.includeAndroidResources = true
+    sourceSets.androidTest.assets.srcDir(copyApkTaskProvider)
     namespace "androidx.wear.watchface.style"
 }
 
diff --git a/wear/watchface/watchface-style/old-api-test-service/build.gradle b/wear/watchface/watchface-style/old-api-test-service/build.gradle
new file mode 100644
index 0000000..6d668ff
--- /dev/null
+++ b/wear/watchface/watchface-style/old-api-test-service/build.gradle
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+import androidx.build.LibraryType
+import com.android.build.api.artifact.SingleArtifact
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.application")
+    id("kotlin-android")
+}
+
+dependencies {
+    compileOnly(project(":annotation:annotation-sampled"))
+
+    api(project(":wear:watchface:watchface-style-old-api-test-stub"))
+
+    implementation("androidx.core:core:1.1.0")
+    implementation("androidx.wear.watchface:watchface-style:1.0.0")
+    implementation("androidx.wear.watchface:watchface-data:1.0.0")
+    api(libs.kotlinStdlib)
+}
+
+androidx {
+    name = "AndroidX WatchFace Style Old Api Test Service"
+    type = LibraryType.SAMPLES
+    inceptionYear = "2022"
+    description = "Test service built with v1.0.0 of the API, used to check for binary AIDL compat"
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 26
+    }
+    buildTypes {
+        release {
+            minifyEnabled true
+            shrinkResources true
+            proguardFiles getDefaultProguardFile('proguard-android.txt')
+        }
+    }
+    buildFeatures {
+        aidl = true
+    }
+    namespace "androidx.wear.watchface.style.test.oldApiTestService"
+}
+
+/*
+ * Allow integration tests to consume the APK produced by this project
+ */
+configurations {
+    apkAssets {
+        canBeConsumed = true
+        canBeResolved = false
+        attributes {
+            attribute(
+                    LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE,
+                    objects.named(LibraryElements, 'apkAssets')
+            )
+        }
+    }
+}
+
+androidComponents {
+    onVariants(selector().all().withBuildType("release"), { variant ->
+        artifacts {
+            apkAssets(variant.artifacts.get(SingleArtifact.APK.INSTANCE))
+        }
+    })
+}
\ No newline at end of file
diff --git a/wear/watchface/watchface-style/old-api-test-service/src/main/AndroidManifest.xml b/wear/watchface/watchface-style/old-api-test-service/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..9d39564
--- /dev/null
+++ b/wear/watchface/watchface-style/old-api-test-service/src/main/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+    <application android:requestLegacyExternalStorage="true">
+        <service android:name="androidx.wear.watchface.style.test.StyleEchoService"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="com.google.android.wearable.action.TEST"/>
+            </intent-filter>
+        </service>
+    </application>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+</manifest>
diff --git a/wear/watchface/watchface-style/old-api-test-service/src/main/java/androidx/wear/watchface/style/test/StyleEchoService.kt b/wear/watchface/watchface-style/old-api-test-service/src/main/java/androidx/wear/watchface/style/test/StyleEchoService.kt
new file mode 100644
index 0000000..a38577b
--- /dev/null
+++ b/wear/watchface/watchface-style/old-api-test-service/src/main/java/androidx/wear/watchface/style/test/StyleEchoService.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.watchface.style.test
+
+import android.app.Service
+import android.content.Intent
+import android.os.IBinder
+import android.util.Log
+import androidx.wear.watchface.style.data.UserStyleSchemaWireFormat
+import androidx.wear.watchface.style.UserStyleSchema
+
+public class StyleEchoService : Service() {
+    override fun onBind(intent: Intent?): IBinder = IStyleEchoServiceStub()
+}
+
+public open class IStyleEchoServiceStub : IStyleEchoService.Stub() {
+    override fun roundTripToApiUserStyleSchemaWireFormat(
+        styleSchema: UserStyleSchemaWireFormat
+    ): UserStyleSchemaWireFormat {
+        val apiUserStyleSchema = UserStyleSchema(styleSchema)
+        Log.i(TAG, "Received $apiUserStyleSchema")
+        return apiUserStyleSchema.toWireFormat()
+    }
+
+    companion object {
+        const val TAG = "IStyleEchoServiceStub"
+    }
+}
\ No newline at end of file
diff --git a/wear/watchface/watchface-style/old-api-test-stub/build.gradle b/wear/watchface/watchface-style/old-api-test-stub/build.gradle
new file mode 100644
index 0000000..ba36ffc
--- /dev/null
+++ b/wear/watchface/watchface-style/old-api-test-stub/build.gradle
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+import androidx.build.LibraryType
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("kotlin-android")
+}
+
+dependencies {
+    compileOnly(project(":annotation:annotation-sampled"))
+
+    implementation("androidx.core:core:1.1.0")
+    implementation("androidx.wear.watchface:watchface-data:1.0.0")
+    api(libs.kotlinStdlib)
+}
+
+androidx {
+    name = "AndroidX WatchFace Style Old Api Test Stub"
+    type = LibraryType.INTERNAL_TEST_LIBRARY
+    inceptionYear = "2022"
+    description = "Test stub built with v1.0.0 of the API, used to check for binary AIDL compat"
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 26
+    }
+    buildFeatures {
+        aidl = true
+    }
+    namespace "androidx.wear.watchface.style.test.oldApiTestStub"
+}
diff --git a/wear/watchface/watchface-style/old-api-test-stub/src/main/AndroidManifest.xml b/wear/watchface/watchface-style/old-api-test-stub/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..9a20520
--- /dev/null
+++ b/wear/watchface/watchface-style/old-api-test-stub/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"/>
diff --git a/wear/watchface/watchface-style/old-api-test-stub/src/main/aidl/androidx/wear/watchface/style/test/IStyleEchoService.aidl b/wear/watchface/watchface-style/old-api-test-stub/src/main/aidl/androidx/wear/watchface/style/test/IStyleEchoService.aidl
new file mode 100644
index 0000000..51fefd7
--- /dev/null
+++ b/wear/watchface/watchface-style/old-api-test-stub/src/main/aidl/androidx/wear/watchface/style/test/IStyleEchoService.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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.watchface.style.test;
+
+import androidx.wear.watchface.style.data.UserStyleSchemaWireFormat;
+
+/**
+ * Interface of a service that allows testing of IPC round trips vs an old binary.
+ *
+ * @hide
+ */
+interface IStyleEchoService {
+    /**
+     * Returns the UserStyleSchemaWireFormat after converting to and from API format.
+     */
+    UserStyleSchemaWireFormat roundTripToApiUserStyleSchemaWireFormat(
+        in UserStyleSchemaWireFormat styleSchema) = 0;
+}
diff --git a/wear/watchface/watchface-style/src/androidTest/AndroidManifest.xml b/wear/watchface/watchface-style/src/androidTest/AndroidManifest.xml
index 91eee54..872539b 100644
--- a/wear/watchface/watchface-style/src/androidTest/AndroidManifest.xml
+++ b/wear/watchface/watchface-style/src/androidTest/AndroidManifest.xml
@@ -14,4 +14,8 @@
   See the License for the specific language governing permissions and
   limitations under the License.
   -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+    <queries>
+        <package android:name="androidx.wear.watchface.style.test.oldApiTestService"/>
+    </queries>
+</manifest>
diff --git a/wear/watchface/watchface-style/src/androidTest/java/androidx/wear/watchface/style/OldClientAidlCompatTest.kt b/wear/watchface/watchface-style/src/androidTest/java/androidx/wear/watchface/style/OldClientAidlCompatTest.kt
new file mode 100644
index 0000000..38680ef
--- /dev/null
+++ b/wear/watchface/watchface-style/src/androidTest/java/androidx/wear/watchface/style/OldClientAidlCompatTest.kt
@@ -0,0 +1,229 @@
+/*
+ * 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.watchface.style
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.graphics.drawable.Icon
+import android.os.IBinder
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.LargeTest
+import androidx.test.rule.ServiceTestRule
+import androidx.wear.watchface.style.test.IStyleEchoService
+import androidx.wear.watchface.style.UserStyleSetting.ComplicationSlotsUserStyleSetting
+import androidx.wear.watchface.style.UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotOverlay
+import androidx.wear.watchface.style.UserStyleSetting.ComplicationSlotsUserStyleSetting.ComplicationSlotsOption
+import androidx.wear.watchface.style.UserStyleSetting.ListUserStyleSetting
+import androidx.wear.watchface.style.UserStyleSetting.Option
+import androidx.wear.watchface.style.test.R
+import com.google.common.truth.Truth.assertThat
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+@LargeTest
+class OldClientAidlCompatTest {
+    @get:Rule
+    val serviceRule = ServiceTestRule()
+
+    companion object {
+        private val CONTEXT: Context = ApplicationProvider.getApplicationContext()
+
+        private val COLOR_STYLE_SETTING = ListUserStyleSetting(
+            UserStyleSetting.Id("COLOR_STYLE_SETTING"),
+            CONTEXT.resources,
+            R.string.colors_style_setting,
+            R.string.colors_style_setting_description,
+            icon = null,
+            options = listOf(
+                ListUserStyleSetting.ListOption(
+                    Option.Id("RED_STYLE"),
+                    CONTEXT.resources,
+                    R.string.colors_style_red,
+                    R.string.colors_style_red_screen_reader,
+                    Icon.createWithResource(CONTEXT, R.drawable.red_style)
+                ),
+                ListUserStyleSetting.ListOption(
+                    Option.Id("GREEN_STYLE"),
+                    CONTEXT.resources,
+                    R.string.colors_style_green,
+                    R.string.colors_style_green_screen_reader,
+                    Icon.createWithResource(CONTEXT, R.drawable.green_style)
+                ),
+                ListUserStyleSetting.ListOption(
+                    Option.Id("BLUE_STYLE"),
+                    CONTEXT.resources,
+                    R.string.colors_style_blue,
+                    R.string.colors_style_blue_screen_reader,
+                    Icon.createWithResource(CONTEXT, R.drawable.blue_style)
+                )
+            ),
+            listOf(
+                WatchFaceLayer.BASE,
+                WatchFaceLayer.COMPLICATIONS,
+                WatchFaceLayer.COMPLICATIONS_OVERLAY
+            )
+        )
+
+        private val DRAW_HOUR_PIPS_SETTING = UserStyleSetting.BooleanUserStyleSetting(
+            UserStyleSetting.Id("DRAW_HOUR_PIPS_STYLE_SETTING"),
+            CONTEXT.resources,
+            R.string.watchface_pips_setting,
+            R.string.watchface_pips_setting_description,
+            icon = null,
+            listOf(WatchFaceLayer.BASE),
+            true
+        )
+
+        private val WATCH_HAND_LENGTH_SETTING = UserStyleSetting.DoubleRangeUserStyleSetting(
+            UserStyleSetting.Id("WATCH_HAND_LENGTH_STYLE_SETTING"),
+            CONTEXT.resources,
+            R.string.watchface_hand_length_setting,
+            R.string.watchface_hand_length_setting_description,
+            icon = null,
+            minimumValue = 0.25,
+            maximumValue = 1.0,
+            listOf(WatchFaceLayer.COMPLICATIONS_OVERLAY),
+            defaultValue = 0.75
+        )
+
+        @Suppress("Deprecation")
+        private val COMPLICATIONS_STYLE_SETTING = ComplicationSlotsUserStyleSetting(
+            UserStyleSetting.Id("COMPLICATIONS_STYLE_SETTING"),
+            CONTEXT.resources,
+            R.string.watchface_complications_setting,
+            R.string.watchface_complications_setting_description,
+            icon = null,
+            complicationConfig = listOf(
+                ComplicationSlotsOption(
+                    Option.Id("LEFT_AND_RIGHT_COMPLICATIONS"),
+                    CONTEXT.resources,
+                    R.string.watchface_complications_setting_both,
+                    icon = null,
+                    // NB this list is empty because each [ComplicationSlotOverlay] is applied on
+                    // top of the initial config.
+                    listOf()
+                ),
+                ComplicationSlotsOption(
+                    Option.Id("NO_COMPLICATIONS"),
+                    CONTEXT.resources,
+                    R.string.watchface_complications_setting_none,
+                    icon = null,
+                    listOf(
+                        ComplicationSlotOverlay(complicationSlotId = 1, enabled = false),
+                        ComplicationSlotOverlay(complicationSlotId = 2, enabled = false)
+                    )
+                ),
+                ComplicationSlotsOption(
+                    Option.Id("LEFT_COMPLICATION"),
+                    CONTEXT.resources,
+                    R.string.watchface_complications_setting_left,
+                    icon = null,
+                    listOf(ComplicationSlotOverlay(complicationSlotId = 1, enabled = false))
+                ),
+                ComplicationSlotsOption(
+                    Option.Id("RIGHT_COMPLICATION"),
+                    CONTEXT.resources,
+                    R.string.watchface_complications_setting_right,
+                    icon = null,
+                    listOf(ComplicationSlotOverlay(complicationSlotId = 2, enabled = false))
+                )
+            ),
+            listOf(WatchFaceLayer.COMPLICATIONS)
+        )
+
+        private val LONG_RANGE_SETTING = UserStyleSetting.LongRangeUserStyleSetting(
+            UserStyleSetting.Id("HOURS_DRAW_FREQ_STYLE_SETTING"),
+            CONTEXT.resources,
+            R.string.watchface_draw_hours_freq_setting,
+            R.string.watchface_draw_hours_freq_setting_description,
+            icon = null,
+            minimumValue = 0,
+            maximumValue = 4,
+            listOf(WatchFaceLayer.BASE),
+            defaultValue = 1
+        )
+
+        private val SCHEMA = UserStyleSchema(
+            listOf(
+                COLOR_STYLE_SETTING,
+                DRAW_HOUR_PIPS_SETTING,
+                WATCH_HAND_LENGTH_SETTING,
+                COMPLICATIONS_STYLE_SETTING,
+                LONG_RANGE_SETTING
+            )
+        )
+
+        private const val ACTON = "com.google.android.wearable.action.TEST"
+        private const val TEXT_FIXTURE_APK = "watchface-style-old-api-test-service-release.apk"
+        private const val PACKAGE_NAME = "androidx.wear.watchface.style.test.oldApiTestService"
+    }
+
+    @Before
+    fun setUp() = withPackageName(PACKAGE_NAME) {
+        install(TEXT_FIXTURE_APK)
+    }
+
+    @After
+    fun tearDown() = withPackageName(PACKAGE_NAME) {
+        uninstall()
+    }
+
+    @Test
+    fun roundTripUserStyleSchema() = runBlocking {
+        val service = IStyleEchoService.Stub.asInterface(
+            bindService(Intent(ACTON).apply { setPackage(PACKAGE_NAME) })
+        )
+
+        val result = UserStyleSchema(
+            service.roundTripToApiUserStyleSchemaWireFormat(SCHEMA.toWireFormat())
+        )
+
+        // We expect five root settings back. Some of the details will have been clipped which
+        // is expected because not all the current features are supported by v1.1.1, the main
+        // thing is the service didn't crash!
+        assertThat(result.rootUserStyleSettings.size).isEqualTo(5)
+    }
+
+    private suspend fun bindService(intent: Intent): IBinder = suspendCoroutine { continuation ->
+        val bound = CONTEXT.bindService(
+            intent,
+            object : ServiceConnection {
+                override fun onServiceConnected(componentName: ComponentName, binder: IBinder) {
+                    continuation.resume(binder)
+                }
+
+                override fun onServiceDisconnected(p0: ComponentName?) {
+                }
+            },
+            Context.BIND_AUTO_CREATE or Context.BIND_IMPORTANT
+        )
+
+        if (!bound) {
+            continuation.resumeWithException(
+                IllegalStateException("No service found for $intent.")
+            )
+        }
+    }
+}
diff --git a/wear/watchface/watchface-style/src/androidTest/java/androidx/wear/watchface/style/TestHelper.kt b/wear/watchface/watchface-style/src/androidTest/java/androidx/wear/watchface/style/TestHelper.kt
new file mode 100644
index 0000000..2f90085
--- /dev/null
+++ b/wear/watchface/watchface-style/src/androidTest/java/androidx/wear/watchface/style/TestHelper.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.watchface.style
+
+import android.os.Build
+import android.os.Environment
+import android.os.ParcelFileDescriptor
+import android.util.Log
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth
+import java.io.File
+
+// Test helpers forked from http://aosp/2218950
+
+fun withPackageName(packageName: String, block: WithPackageBlock.() -> Unit) {
+    block(WithPackageBlock(packageName))
+}
+
+class WithPackageBlock internal constructor(private val packageName: String) {
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val uiAutomation = instrumentation.uiAutomation
+
+    // `externalMediaDirs` is deprecated. Docs encourage to put media in MediaStore instead. Here
+    // we just need a folder that can be accessed by both app and shell user so should be fine.
+    @Suppress("deprecation")
+    private val dirUsableByAppAndShell by lazy {
+        when {
+            Build.VERSION.SDK_INT >= 29 -> {
+                // On Android Q+ we are using the media directory because that is
+                // the directory that the shell has access to. Context: b/181601156
+                // This is the same logic of Outputs#init to determine `dirUsableByAppAndShell`.
+                InstrumentationRegistry.getInstrumentation().context.externalMediaDirs.firstOrNull {
+                    Environment.getExternalStorageState(it) == Environment.MEDIA_MOUNTED
+                }
+            }
+            Build.VERSION.SDK_INT <= 22 -> {
+                // prior to API 23, shell didn't have access to externalCacheDir
+                InstrumentationRegistry.getInstrumentation().context.cacheDir
+            }
+            else -> InstrumentationRegistry.getInstrumentation().context.externalCacheDir
+        } ?: throw IllegalStateException("Unable to select a directory for writing files.")
+    }
+
+    public fun uninstall() = executeCommand("pm uninstall $packageName")
+
+    public fun install(apkName: String) {
+        // Contains all the clean up actions to perform at the end of the execution
+        val cleanUpBlocks = mutableListOf<() -> (Unit)>()
+
+        try {
+            // First writes in a temp file the apk from the assets
+            val tmpApkFile = File(dirUsableByAppAndShell, "tmp_$apkName").also { file ->
+                file.delete()
+                file.createNewFile()
+                file.deleteOnExit()
+                file.outputStream().use { instrumentation.context.assets.open(apkName).copyTo(it) }
+            }
+            cleanUpBlocks.add { tmpApkFile.delete() }
+
+            // Then moves it to a destination that can be used to install it
+            val destApkPath = "$TEMP_DIR/$apkName"
+            Truth.assertThat(executeCommand("mv ${tmpApkFile.absolutePath} $destApkPath")).isEmpty()
+            cleanUpBlocks.add { executeCommand("rm $destApkPath") }
+
+            // This mimes the behaviour of `adb install-multiple` using an install session.
+            // For reference:
+            // https://source.corp.google.com/android-internal/packages/modules/adb/client/adb_install.cpp
+
+            // Creates an install session
+            val installCreateOutput = executeCommand("pm install-create -t").first().trim()
+            val sessionId = REGEX_SESSION_ID
+                .find(installCreateOutput)!!
+                .groups[1]!!
+                .value
+                .toLong()
+
+            // Adds the base apk to the install session
+            val successBaseApk =
+                executeCommand("pm install-write $sessionId base.apk $TEMP_DIR/$apkName")
+                    .first()
+                    .trim()
+                    .startsWith("Success")
+            if (!successBaseApk) {
+                throw IllegalStateException("Could not add $apkName to install session $sessionId")
+            }
+
+            // Commit the install transaction. Note that install-commit may not print any output
+            // if it fails.
+            val commitCommandOutput = executeCommand("pm install-commit $sessionId")
+            val firstLine = commitCommandOutput.firstOrNull()?.trim()
+            if (firstLine == null || firstLine != "Success") {
+                throw IllegalStateException(
+                    "pm install-commit failed: ${commitCommandOutput.joinToString("\n")}"
+                )
+            }
+        } finally {
+            // Runs all the clean up blocks. This will clean up also partial operations in case
+            // there is an issue during install
+            cleanUpBlocks.forEach { it() }
+        }
+    }
+
+    private fun executeCommand(command: String): List<String> {
+        Log.d(TAG, "Executing command: `$command`")
+        return ParcelFileDescriptor.AutoCloseInputStream(uiAutomation.executeShellCommand(command))
+            .bufferedReader()
+            .lineSequence()
+            .toList()
+    }
+
+    companion object {
+        private const val TAG = "TestManager"
+        private const val TEMP_DIR = "/data/local/tmp/"
+        private val REGEX_SESSION_ID = """\[(\d+)\]""".toRegex()
+    }
+}
\ No newline at end of file
diff --git a/wear/watchface/watchface-style/src/androidTest/java/androidx/wear/watchface/style/UserStyleSettingWithStringResourcesTest.kt b/wear/watchface/watchface-style/src/androidTest/java/androidx/wear/watchface/style/UserStyleSettingWithStringResourcesTest.kt
index 8702747..b88eadc 100644
--- a/wear/watchface/watchface-style/src/androidTest/java/androidx/wear/watchface/style/UserStyleSettingWithStringResourcesTest.kt
+++ b/wear/watchface/watchface-style/src/androidTest/java/androidx/wear/watchface/style/UserStyleSettingWithStringResourcesTest.kt
@@ -162,6 +162,36 @@
     }
 
     @Test
+    @Suppress("deprecation")
+    public fun listUserStyleSettingWireFormatRoundTrip_noScreenReaderName() {
+        val listUserStyleSetting = ListUserStyleSetting(
+            UserStyleSetting.Id("list"),
+            context.resources,
+            R.string.colors_style_setting,
+            R.string.colors_style_setting_description,
+            icon = null,
+            options = listOf(
+                ListOption(
+                    UserStyleSetting.Option.Id("one"),
+                    context.resources,
+                    R.string.ith_option,
+                    icon = null
+                )
+            ),
+            listOf(WatchFaceLayer.BASE, WatchFaceLayer.COMPLICATIONS_OVERLAY)
+        )
+
+        val listUserStyleSettingAfterRoundTrip = ListUserStyleSetting(
+            listUserStyleSetting.toWireFormat()
+        )
+
+        val option0 = listUserStyleSettingAfterRoundTrip.options[0] as ListOption
+        Truth.assertThat(option0.displayName).isEqualTo("1st option")
+        // We expect screenReaderName to be back filled by the displayName.
+        Truth.assertThat(option0.screenReaderName).isEqualTo("1st option")
+    }
+
+    @Test
     public fun complicationSlotsOptionsWithIndices() {
         val complicationSetting = ComplicationSlotsUserStyleSetting(
             UserStyleSetting.Id("complications_style_setting1"),
@@ -209,4 +239,35 @@
         Truth.assertThat(option2.displayName).isEqualTo("3rd option")
         Truth.assertThat(option2.screenReaderName).isEqualTo("3rd list option")
     }
+
+    @Test
+    @Suppress("deprecation")
+    public fun
+    complicationsUserStyleSettingWireFormatRoundTrip_noScreenReaderName_filledByDisplayName() {
+        val complicationSetting = ComplicationSlotsUserStyleSetting(
+            UserStyleSetting.Id("complications_style_setting1"),
+            displayName = "Complications",
+            description = "Number and position",
+            icon = null,
+            complicationConfig = listOf(
+                ComplicationSlotsOption(
+                    UserStyleSetting.Option.Id("one"),
+                    context.resources,
+                    displayNameResourceId = R.string.ith_option,
+                    icon = null,
+                    emptyList()
+                )
+            ),
+            listOf(WatchFaceLayer.COMPLICATIONS)
+        )
+
+        val complicationSettingAfterRoundTrip = ComplicationSlotsUserStyleSetting(
+            complicationSetting.toWireFormat()
+        )
+
+        val option0 = complicationSettingAfterRoundTrip.options[0] as ComplicationSlotsOption
+        Truth.assertThat(option0.displayName).isEqualTo("1st option")
+        // We expect screenReaderName to be back filled by the displayName.
+        Truth.assertThat(option0.screenReaderName).isEqualTo("1st option")
+    }
 }
\ No newline at end of file
diff --git a/wear/watchface/watchface-style/src/androidTest/res/drawable-nodpi/blue_style.png b/wear/watchface/watchface-style/src/androidTest/res/drawable-nodpi/blue_style.png
new file mode 100644
index 0000000..2a8e5f0
--- /dev/null
+++ b/wear/watchface/watchface-style/src/androidTest/res/drawable-nodpi/blue_style.png
Binary files differ
diff --git a/wear/watchface/watchface-style/src/androidTest/res/drawable-nodpi/green_style.png b/wear/watchface/watchface-style/src/androidTest/res/drawable-nodpi/green_style.png
new file mode 100644
index 0000000..69166e3
--- /dev/null
+++ b/wear/watchface/watchface-style/src/androidTest/res/drawable-nodpi/green_style.png
Binary files differ
diff --git a/wear/watchface/watchface-style/src/androidTest/res/drawable-nodpi/red_style.png b/wear/watchface/watchface-style/src/androidTest/res/drawable-nodpi/red_style.png
new file mode 100644
index 0000000..a328129
--- /dev/null
+++ b/wear/watchface/watchface-style/src/androidTest/res/drawable-nodpi/red_style.png
Binary files differ
diff --git a/wear/watchface/watchface-style/src/androidTest/res/values/strings.xml b/wear/watchface/watchface-style/src/androidTest/res/values/strings.xml
index 1510cb7..3133a45 100644
--- a/wear/watchface/watchface-style/src/androidTest/res/values/strings.xml
+++ b/wear/watchface/watchface-style/src/androidTest/res/values/strings.xml
@@ -35,4 +35,228 @@
 
     <string name="ith_option" translatable="false">%1$s option</string>
     <string name="ith_option_screen_reader_name" translatable="false">%1$s list option</string>
+
+    <!-- An option within the watch face color style theme settings [CHAR LIMIT=20] -->
+    <string name="colors_style_red">Red</string>
+
+    <!-- An option within the watch face color style theme settings [CHAR LIMIT=20] -->
+    <string name="colors_style_green">Green</string>
+
+    <!-- An option within the watch face color style theme settings [CHAR LIMIT=20] -->
+    <string name="colors_style_blue">Blue</string>
+
+    <!-- An option within the watch face color style theme settings -->
+    <string name="colors_style_red_screen_reader">Red color theme</string>
+
+    <!-- An option within the watch face color style theme settings -->
+    <string name="colors_style_green_screen_reader">Green color theme</string>
+
+    <!-- An option within the watch face color style theme settings -->
+    <string name="colors_style_blue_screen_reader">Blue color theme</string>
+
+    <!-- Name of watchface style category for selecting the hand style [CHAR LIMIT=20] -->
+    <string name="hand_style_setting" translatable="false">Hand Style</string>
+
+    <!-- Subtitle for the menu option to select the watch face hand style [CHAR LIMIT=20] -->
+    <string name="hand_style_setting_description" translatable="false">Hand visual look</string>
+
+    <!-- An option with the watch face hand style settings [CHAR LIMIT=20] -->
+    <string name="hand_style_classic" translatable="false">Classic</string>
+
+    <!-- An option with the watch face hand style settings  for use by screen readers -->
+    <string name="hand_style_classic_screen_reader" translatable="false">Classic watch hand
+    style</string>
+
+    <!-- An option with the watch face hand style settings [CHAR LIMIT=20] -->
+    <string name="hand_style_modern" translatable="false">Modern</string>
+
+    <!-- An option with the watch face hand style settings  for use by screen readers -->
+    <string name="hand_style_modern_screen_reader" translatable="false">Modern watch hand
+    style</string>
+
+    <!-- An option with the watch face hand style settings [CHAR LIMIT=20] -->
+    <string name="hand_style_gothic" translatable="false">Gothic</string>
+
+    <!-- An option with the watch face hand style settings for use by screen readers -->
+    <string name="hand_style_gothic_screen_reader" translatable="false">Gothic watch hand
+    style</string>
+
+    <!-- An option within the analog watch face to draw pips to mark each hour [CHAR LIMIT=20] -->
+    <string name="watchface_pips_setting">Hour Pips</string>
+
+    <!-- Subtitle for the menu option to configure if we should draw pips to mark each hour
+     on the watch face [CHAR LIMIT=20] -->
+    <string name="watchface_pips_setting_description">Whether to draw or not</string>
+
+    <!-- A menu option to select a widget that lets us configure the length of the watch hands
+    [CHAR LIMIT=20] -->
+    <string name="watchface_hand_length_setting">Hand length</string>
+
+    <!-- Sub title for the menu option to select a widget that lets us configure the length of the
+    watch hands [CHAR LIMIT=20] -->
+    <string name="watchface_hand_length_setting_description">Scale of watch hands</string>
+
+    <!-- A menu option to select a widget that lets us configure the Complications (a watch
+    making term) [CHAR LIMIT=20] -->
+    <string name="watchface_complications_setting">Complications</string>
+
+    <!-- Sub title for the menu option to select a widget that lets us configure the Complications
+    (a watch making term) [CHAR LIMIT=20] -->
+    <string name="watchface_complications_setting_description">Number and position</string>
+
+    <!-- Menu option within the complications configuration widget that lets us select both
+    the left and the right complication for rendering. [CHAR LIMIT=20] -->
+    <string name="watchface_complications_setting_both">Both</string>
+
+    <!-- Menu option within the complications configuration widget that lets us select no
+    complications for rendering. [CHAR LIMIT=20] -->
+    <string name="watchface_complications_setting_none">None</string>
+
+    <!-- Menu option within the complications configuration widget that lets us select only the left
+    complication for rendering. [CHAR LIMIT=20] -->
+    <string name="watchface_complications_setting_left">Left</string>
+
+    <!-- Menu option within the complications configuration widget that lets us select only the
+    right complication for rendering. [CHAR LIMIT=20] -->
+    <string name="watchface_complications_setting_right">Right</string>
+
+    <!-- Menu option to select a widget that lets us configure the frequency of hours labeling
+    [CHAR LIMIT=20] -->
+    <string name="watchface_draw_hours_freq_setting">Hours labeling</string>
+
+    <!-- Sub title for the menu option to select a widget that lets us configure the frequency of
+    hours labeling [CHAR LIMIT=20] -->
+    <string name="watchface_draw_hours_freq_setting_description">Labeling frequency</string>
+
+    <!-- Menu option to select a widget that lets us configure whether to show hour pips
+    [CHAR LIMIT=20] -->
+    <string name="watchface_draw_hours_setting">Hour pips</string>
+
+    <!-- Sub title for the menu option to select a widget that lets us configure whether to show
+    hour pips [CHAR LIMIT=20] -->
+    <string name="watchface_draw_hours_setting_description">Display on/off</string>
+
+    <!-- Name of the left complication for use visually in the companion editor. [CHAR LIMIT=20] -->
+    <string name="left_complication_screen_name">Left</string>
+
+    <!-- Name of the left complication for use by in the companion editor screen reader. -->
+    <string name="left_complication_screen_reader_name">Left complication.</string>
+
+    <!-- Name of the right complication for use visually in the companion editor.
+    [CHAR LIMIT=20] -->
+    <string name="right_complication_screen_name">Right</string>
+
+    <!-- Name of the right complication for use by the companion editor screen reader. -->
+    <string name="right_complication_screen_reader_name">Right complication.</string>
+
+    <!-- Name of a complication at the top of the screen, for use visually in the companion editor.
+    [CHAR LIMIT=20] -->
+    <string name="upper_complication_screen_name">Upper</string>
+
+    <!-- Name of a complication at the top of the screen, for use by the companion editor screen
+    reader. -->
+    <string name="upper_complication_screen_reader_name">Upper complication.</string>
+
+    <!-- Name of a complication at the bottom of the screen, for use visually in the companion
+    editor. [CHAR LIMIT=20] -->
+    <string name="lower_complication_screen_name">Lower</string>
+
+    <!-- Name of a complication at the bottom of the screen, for use by the companion editor screen
+    reader. -->
+    <string name="lower_complication_screen_reader_name">Lower complication.</string>
+
+    <!-- Name of the optional background complication which is drawn behind the watchface, for use
+    visually in the companion editor. [CHAR LIMIT=20] -->
+    <string name="background_complication_screen_name">Background</string>
+
+    <!-- Name of the optional background complication which is drawn behind the watchface, for use
+    by the companion editor screen reader. -->
+    <string name="background_complication_screen_reader_name">Background complication.</string>
+
+    <!-- Name of a style that controls the appearance of a digital clock [CHAR LIMIT=20] -->
+    <string name="digital_clock_style_name">Clock style</string>
+
+    <!-- Sub title for the menu option to select a widget that lets us configure the style of a
+    digital watch [CHAR LIMIT=20] -->
+    <string name="digital_clock_style_description">Clock style setting</string>
+
+    <!-- Menu option for a 12 hour digital clock display. [CHAR LIMIT=20] -->
+    <string name="digital_clock_style_12">12</string>
+
+    <!-- Menu option for a 12 hour digital clock display. [CHAR LIMIT=20] -->
+    <string name="digital_clock_style_12_screen_reader">12 hour clock</string>
+
+    <!-- Menu option for a 24 hour digital clock display used by screen reader. -->
+    <string name="digital_clock_style_24">24</string>
+
+    <!-- Menu option for a 24 hour digital clock display used by screen reader.-->
+    <string name="digital_clock_style_24_screen_reader">24 hour clock</string>
+
+    <!-- Menu option for selecting a digital clock [CHAR LIMIT=20] -->
+    <string name="style_digital_watch">Digital</string>
+
+    <!-- Menu option for selecting a digital clock from a screen reader. -->
+    <string name="style_digital_watch_screen_reader">Digital watch style</string>
+
+    <!-- Menu option for selecting an analog clock [CHAR LIMIT=20] -->
+    <string name="style_analog_watch">Analog</string>
+
+    <!-- Menu option for selecting an analog clock  from a screen reader.-->
+    <string name="style_analog_watch_screen_reader">Analog watch style </string>
+
+    <!-- Title for the menu option to select an analog or digital clock [CHAR LIMIT=20] -->
+    <string name="clock_type">Clock type</string>
+
+    <!-- Sub title for the menu option to select an analog or digital clock [CHAR LIMIT=20] -->
+    <string name="clock_type_description">Select analog or digital</string>
+
+    <!-- A menu option to select a widget that lets us configure the Complications (a watch
+    making term) for the digital watch [CHAR LIMIT=20] -->
+    <string name="digital_complications_setting">Complication</string>
+
+    <!-- Sub title for the menu option to select a widget that lets us configure the Complications
+    (a watch making term) for the digital watch [CHAR LIMIT=20] -->
+    <string name="digital_complications_setting_description">On or off</string>
+
+    <!-- List entry, enabling the complication for the digital watch. [CHAR LIMIT=20] -->
+    <string name="digital_complication_on_screen_name">On</string>
+
+    <!-- List entry, disabling the complication for the digital watch. [CHAR LIMIT=20] -->
+    <string name="digital_complication_off_screen_name">Off</string>
+
+    <!-- List entry setting the number of complications enabled for the analog watch.
+    [CHAR LIMIT=20] -->
+    <string name="analog_complication_one_screen_name">One</string>
+
+    <!-- List entry setting the number of complications enabled for the analog watch.
+    [CHAR LIMIT=20] -->
+    <string name="analog_complication_two_screen_name">Two</string>
+
+    <!-- List entry setting the number of complications enabled for the analog watch.
+    [CHAR LIMIT=20] -->
+    <string name="analog_complication_three_screen_name">Three</string>
+
+    <!-- Name of a complication at the top of the screen, for use visually in the companion editor.
+    [CHAR LIMIT=20] -->
+    <string name="hierarchical_complication1_screen_name">Top</string>
+
+    <!-- Name of a complication at the top of the screen, for use by the companion editor screen
+    reader. -->
+    <string name="hierarchical_complication1_screen_reader_name">Top complication</string>
+
+    <!-- Name of a complication at the middle of the screen, for use visually in the companion editor.
+    [CHAR LIMIT=20] -->
+    <string name="hierarchical_complication2_screen_name">Middle</string>
+
+    <!-- Name of a complication at the middle of the screen, for use by the companion editor screen
+    reader. -->
+    <string name="hierarchical_complication2_screen_reader_name">Middle complication</string>
+
+    <!-- Name of a complication at the bottom of the screen, for use visually in the companion editor.
+    [CHAR LIMIT=20] -->
+    <string name="hierarchical_complication3_screen_name">Bottom</string>
+
+    <!-- Name of a complication at the bottom of the screen, for use by the companion editor screen
+    reader. -->
+    <string name="hierarchical_complication3_screen_reader_name">Bottom complication</string>
 </resources>
\ No newline at end of file
diff --git a/wear/watchface/watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt b/wear/watchface/watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
index 84ead0a..483ff43 100644
--- a/wear/watchface/watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
+++ b/wear/watchface/watchface-style/src/main/java/androidx/wear/watchface/style/UserStyleSetting.kt
@@ -1419,7 +1419,10 @@
                 options.map {
                     (it as ComplicationSlotsOption).watchFaceEditorData?.toWireFormat() ?: Bundle()
                 },
-                options.map { (it as ComplicationSlotsOption).screenReaderName }
+                options.map {
+                    it as ComplicationSlotsOption
+                    it.screenReaderName ?: it.displayName
+                }
             )
 
         internal companion object {
@@ -2216,7 +2219,10 @@
                 affectedWatchFaceLayers.map { it.ordinal },
                 watchFaceEditorData?.toWireFormat(),
                 options.map { (it as ListOption).watchFaceEditorData?.toWireFormat() ?: Bundle() },
-                options.map { (it as ListOption).screenReaderName }
+                options.map {
+                    it as ListOption
+                    it.screenReaderName ?: it.displayName
+                }
             )
 
         internal companion object {
diff --git a/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/ComplicationHelperActivityTest.kt b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/ComplicationHelperActivityTest.kt
index b2f2811..db2ad24 100644
--- a/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/ComplicationHelperActivityTest.kt
+++ b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/ComplicationHelperActivityTest.kt
@@ -22,6 +22,8 @@
 import android.os.Handler
 import android.os.Looper
 import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
 import androidx.wear.watchface.complications.data.ComplicationType
 import androidx.wear.watchface.complications.data.ComplicationType.LONG_TEXT
 import androidx.wear.watchface.complications.data.ComplicationType.MONOCHROMATIC_IMAGE
@@ -34,9 +36,12 @@
 import com.nhaarman.mockitokotlin2.verify
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
+import org.junit.runner.RunWith
 
 const val TIME_OUT_MILLIS = 500L
 
+@RunWith(AndroidJUnit4::class)
+@MediumTest
 public class ComplicationHelperActivityTest {
     private val mainThreadHandler = Handler(Looper.getMainLooper())
 
diff --git a/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/WatchFaceServiceAndroidTest.kt b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/WatchFaceServiceAndroidTest.kt
index 7f3f0ad..3f6fe61 100644
--- a/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/WatchFaceServiceAndroidTest.kt
+++ b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/WatchFaceServiceAndroidTest.kt
@@ -19,12 +19,17 @@
 import android.content.Context
 import android.graphics.drawable.Icon
 import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
 import androidx.wear.watchface.style.UserStyleSchema
 import androidx.wear.watchface.style.UserStyleSetting
 import androidx.wear.watchface.style.WatchFaceLayer
 import androidx.wear.watchface.test.SimpleWatchFaceTestService
 import org.junit.Test
+import org.junit.runner.RunWith
 
+@RunWith(AndroidJUnit4::class)
+@MediumTest
 class WatchFaceServiceAndroidTest {
     @Test
     fun measuresWatchFaceIconsFromCustomContext() {
diff --git a/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/SimpleWatchFaceTestService.kt b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/SimpleWatchFaceTestService.kt
index 77c3d9a..0738ffe 100644
--- a/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/SimpleWatchFaceTestService.kt
+++ b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/SimpleWatchFaceTestService.kt
@@ -16,6 +16,7 @@
 
 package androidx.wear.watchface.test
 
+import android.os.Build
 import android.view.SurfaceHolder
 import androidx.test.core.app.ApplicationProvider
 import androidx.wear.watchface.ComplicationSlotsManager
@@ -42,5 +43,5 @@
     ) = throw NotImplementedError("Should not reach this step")
 
     // Set this to `true` so that the whole setup is skipped for this test
-    override fun isPreAndroidR() = true
+    override val wearSdkVersion = Build.VERSION_CODES.O_MR1
 }
diff --git a/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/TestCanvasAnalogWatchFaceService.kt b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/TestCanvasAnalogWatchFaceService.kt
index 12966a3..217a28a 100644
--- a/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/TestCanvasAnalogWatchFaceService.kt
+++ b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/TestCanvasAnalogWatchFaceService.kt
@@ -17,6 +17,7 @@
 package androidx.wear.watchface.test
 
 import android.content.Context
+import android.os.Build
 import android.os.Handler
 import android.view.SurfaceHolder
 import androidx.wear.watchface.ComplicationSlotsManager
@@ -87,7 +88,10 @@
 
     override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
 
-    override fun isPreAndroidR() = preRInitFlow
+    override val wearSdkVersion = when (preRInitFlow) {
+        true -> Build.VERSION_CODES.O_MR1
+        false -> Build.VERSION_CODES.R
+    }
 
     override fun readDirectBootPrefs(
         context: Context,
diff --git a/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceServiceImageTest.kt b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceServiceImageTest.kt
index 135c472..60c9f35 100644
--- a/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceServiceImageTest.kt
+++ b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceServiceImageTest.kt
@@ -42,16 +42,12 @@
 import androidx.test.filters.MediumTest
 import androidx.test.screenshot.AndroidXScreenshotTestRule
 import androidx.test.screenshot.assertAgainstGolden
-import androidx.wear.watchface.ComplicationSlotsManager
 import androidx.wear.watchface.DrawMode
-import androidx.wear.watchface.MutableWatchState
 import androidx.wear.watchface.RenderParameters
 import androidx.wear.watchface.SYSTEM_SUPPORTS_CONSISTENT_IDS_PREFIX
 import androidx.wear.watchface.TapEvent
 import androidx.wear.watchface.TapType
-import androidx.wear.watchface.WatchFace
 import androidx.wear.watchface.WatchFaceService
-import androidx.wear.watchface.WatchState
 import androidx.wear.watchface.complications.SystemDataSources
 import androidx.wear.watchface.complications.data.ComplicationText
 import androidx.wear.watchface.complications.data.LongTextComplicationData
@@ -75,8 +71,6 @@
 import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService.Companion.EXAMPLE_CANVAS_WATCHFACE_LEFT_COMPLICATION_ID
 import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService.Companion.EXAMPLE_CANVAS_WATCHFACE_RIGHT_COMPLICATION_ID
 import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService.Companion.GREEN_STYLE
-import androidx.wear.watchface.style.CurrentUserStyleRepository
-import androidx.wear.watchface.style.UserStyleSchema
 import androidx.wear.watchface.style.WatchFaceLayer
 import androidx.wear.watchface.style.data.UserStyleWireFormat
 import com.google.common.truth.Truth.assertThat
@@ -85,7 +79,6 @@
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.android.asCoroutineDispatcher
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
@@ -136,74 +129,6 @@
     }
 }
 
-internal class TestControllableWatchFaceService(
-    private val handler: Handler,
-    private var surfaceHolderOverride: SurfaceHolder,
-    private val factory: TestWatchFaceFactory,
-    private val watchState: MutableWatchState,
-    private val directBootParams: WallpaperInteractiveWatchFaceInstanceParams?
-) : WatchFaceService() {
-    init {
-        attachBaseContext(ApplicationProvider.getApplicationContext())
-    }
-
-    abstract class TestWatchFaceFactory {
-        fun createUserStyleSchema(): UserStyleSchema = UserStyleSchema(emptyList())
-
-        fun createComplicationsManager(
-            currentUserStyleRepository: CurrentUserStyleRepository
-        ): ComplicationSlotsManager =
-            ComplicationSlotsManager(emptyList(), currentUserStyleRepository)
-
-        abstract fun createWatchFaceAsync(
-            surfaceHolder: SurfaceHolder,
-            watchState: WatchState,
-            complicationSlotsManager: ComplicationSlotsManager,
-            currentUserStyleRepository: CurrentUserStyleRepository
-        ): Deferred<WatchFace>
-    }
-
-    override fun createUserStyleSchema() = factory.createUserStyleSchema()
-
-    override fun createComplicationSlotsManager(
-        currentUserStyleRepository: CurrentUserStyleRepository
-    ) = factory.createComplicationsManager(currentUserStyleRepository)
-
-    override suspend fun createWatchFace(
-        surfaceHolder: SurfaceHolder,
-        watchState: WatchState,
-        complicationSlotsManager: ComplicationSlotsManager,
-        currentUserStyleRepository: CurrentUserStyleRepository
-    ) = factory.createWatchFaceAsync(
-        surfaceHolderOverride,
-        watchState,
-        complicationSlotsManager,
-        currentUserStyleRepository
-    ).await()
-
-    override fun getUiThreadHandlerImpl() = handler
-
-    override fun getBackgroundThreadHandlerImpl() = handler
-
-    override fun getMutableWatchState() = watchState
-
-    override fun readDirectBootPrefs(
-        context: Context,
-        fileName: String
-    ) = directBootParams
-
-    override fun writeDirectBootPrefs(
-        context: Context,
-        fileName: String,
-        prefs: WallpaperInteractiveWatchFaceInstanceParams
-    ) {
-    }
-
-    override fun isPreAndroidR() = false
-
-    override fun getWallpaperSurfaceHolderOverride() = surfaceHolderOverride
-}
-
 @RunWith(AndroidJUnit4::class)
 @MediumTest
 @RequiresApi(Build.VERSION_CODES.O_MR1)
@@ -424,6 +349,7 @@
         bitmap.assertAgainstGolden(screenshotRule, "active_screenshot")
     }
 
+    @FlakyTest(bugId = 264868778)
     @Test
     public fun testAmbientScreenshot() {
         handler.post(this::initCanvasWatchFace)
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/CancellableUniqueTask.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/CancellableUniqueTask.kt
index ea71d9b..76fdc41 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/CancellableUniqueTask.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/CancellableUniqueTask.kt
@@ -17,6 +17,7 @@
 package androidx.wear.watchface
 
 import android.os.Handler
+import java.time.Duration
 
 /**
  * Task posting helper which allows only one pending task at a time.
@@ -31,17 +32,13 @@
 
     fun isPending() = (pendingTask != null)
 
-    fun postUnique(task: () -> Unit) {
-        postDelayedUnique(0, task)
-    }
-
-    fun postDelayedUnique(delayMillis: Long, task: () -> Unit) {
+    fun postDelayedUnique(delay: Duration, task: () -> Unit) {
         cancel()
         val runnable = Runnable {
             task()
             pendingTask = null
         }
-        handler.postDelayed(runnable, delayMillis)
+        handler.postDelayed(runnable, delay.toMillis())
         pendingTask = runnable
     }
 }
\ No newline at end of file
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
index 40c59a8..541c0da 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
@@ -60,6 +60,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.launch
 import java.security.InvalidParameterException
+import java.time.Duration
 import java.time.Instant
 import java.time.ZoneId
 import java.time.ZonedDateTime
@@ -610,9 +611,6 @@
     internal var lastDrawTimeMillis: Long = 0
     internal var nextDrawTimeMillis: Long = 0
 
-    private val pendingUpdateTime: CancellableUniqueTask =
-        CancellableUniqueTask(watchFaceHostApi.getUiThreadHandler())
-
     internal val componentName =
         ComponentName(
             watchFaceHostApi.getContext().packageName,
@@ -859,7 +857,6 @@
     }
 
     internal fun onDestroy() {
-        pendingUpdateTime.cancel()
         renderer.onDestroyInternal()
         if (!watchState.isHeadless) {
             WatchFace.unregisterEditorDelegate(componentName)
@@ -898,9 +895,7 @@
         }
 
         if (renderer.shouldAnimate()) {
-            pendingUpdateTime.postUnique {
-                watchFaceHostApi.invalidate()
-            }
+            watchFaceHostApi.postInvalidate()
         }
     }
 
@@ -946,18 +941,19 @@
 
         if (renderer.shouldAnimate()) {
             val currentTimeMillis = systemTimeProvider.getSystemTimeMillis()
-            var delay = computeDelayTillNextFrame(startTimeMillis, currentTimeMillis, Instant.now())
-            nextDrawTimeMillis = currentTimeMillis + delay
+            var delayMillis =
+                computeDelayTillNextFrame(startTimeMillis, currentTimeMillis, Instant.now())
+            nextDrawTimeMillis = currentTimeMillis + delayMillis
 
             // We want to post our delayed task to post the choreographer frame a bit earlier than
             // the deadline because if we post it too close to the deadline we'll miss it. If we're
             // close to the deadline we post the choreographer frame immediately.
-            delay -= POST_CHOREOGRAPHER_FRAME_MILLIS_BEFORE_DEADLINE
+            delayMillis -= POST_CHOREOGRAPHER_FRAME_MILLIS_BEFORE_DEADLINE
 
-            if (delay <= 0) {
+            if (delayMillis <= 0) {
                 watchFaceHostApi.invalidate()
             } else {
-                pendingUpdateTime.postDelayedUnique(delay) { watchFaceHostApi.invalidate() }
+                watchFaceHostApi.postInvalidate(Duration.ofMillis(delayMillis))
             }
         }
     }
@@ -1190,7 +1186,6 @@
         writer.println("lastDrawTimeMillis=$lastDrawTimeMillis")
         writer.println("nextDrawTimeMillis=$nextDrawTimeMillis")
         writer.println("muteMode=$muteMode")
-        writer.println("pendingUpdateTime=${pendingUpdateTime.isPending()}")
         writer.println("lastTappedComplicationId=$lastTappedComplicationId")
         writer.println(
             "currentUserStyleRepository.userStyle=${currentUserStyleRepository.userStyle.value}"
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt
index 30b5a82e..32d6ffa 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt
@@ -25,6 +25,7 @@
 import androidx.annotation.UiThread
 import androidx.wear.watchface.complications.SystemDataSources.DataSourceId
 import androidx.wear.watchface.style.data.UserStyleWireFormat
+import java.time.Duration
 import kotlinx.coroutines.CoroutineScope
 
 /**
@@ -124,6 +125,8 @@
     @UiThread
     public fun invalidate()
 
+    public fun postInvalidate(delay: Duration = Duration.ZERO)
+
     /** Intent to launch the complication permission denied activity. */
     public fun getComplicationDeniedIntent(): Intent?
 
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index 9a39ea0..ca08131 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -25,7 +25,6 @@
 import android.graphics.Canvas
 import android.graphics.Rect
 import android.os.Build
-import android.os.Build.VERSION.SDK_INT
 import android.os.Bundle
 import android.os.Handler
 import android.os.HandlerThread
@@ -56,9 +55,9 @@
 import androidx.annotation.WorkerThread
 import androidx.versionedparcelable.ParcelUtils
 import androidx.wear.watchface.complications.SystemDataSources.DataSourceId
-import androidx.wear.watchface.complications.data.ComplicationPersistencePolicies
 import androidx.wear.watchface.complications.data.ComplicationData
 import androidx.wear.watchface.complications.data.ComplicationExperimental
+import androidx.wear.watchface.complications.data.ComplicationPersistencePolicies
 import androidx.wear.watchface.complications.data.ComplicationType
 import androidx.wear.watchface.complications.data.NoDataComplicationData
 import androidx.wear.watchface.complications.data.toApiComplicationData
@@ -95,6 +94,7 @@
 import java.io.ObjectInputStream
 import java.io.ObjectOutputStream
 import java.io.PrintWriter
+import java.time.Duration
 import java.time.Instant
 import java.time.ZoneId
 import kotlinx.coroutines.CancellationException
@@ -363,6 +363,7 @@
             UI,
             CURRENT
         }
+
         /**
          * Waits for deferredValue using runBlocking, then executes the task on the thread
          * specified by executionThread param.
@@ -388,6 +389,7 @@
                                     task(deferredValue)
                                 }
                             }
+
                             ExecutionThread.CURRENT -> {
                                 task(deferredValue)
                             }
@@ -713,10 +715,16 @@
     internal open fun allowWatchFaceToAnimate() = true
 
     /**
-     * Whether or not the pre R style init flow (SET_BINDER wallpaper command) is expected.
-     * This is open for use by tests.
+     * Equivalent to [Build.VERSION.SDK_INT], but allows override for any platform-independent
+     * versioning.
+     *
+     * This is meant to only be used in androidTest, which only support testing on one SDK. In
+     * Robolectric tests use `@Config(sdk = [Build.VERSION_CODES.*])`.
+     *
+     * Note that this cannot override platform-dependent versioning, which means inconsistency.
      */
-    internal open fun isPreAndroidR() = Build.VERSION.SDK_INT < Build.VERSION_CODES.R
+    @VisibleForTesting
+    internal open val wearSdkVersion = Build.VERSION.SDK_INT
 
     /** [Choreographer] isn't supposed to be mocked, so we use a thin wrapper. */
     internal interface ChoreographerWrapper {
@@ -727,6 +735,7 @@
     /** This is open to allow mocking. */
     internal open fun getChoreographer(): ChoreographerWrapper = object : ChoreographerWrapper {
         private val choreographer = Choreographer.getInstance()
+
         init {
             require(Looper.myLooper() == Looper.getMainLooper()) {
                 "Creating choreographer not on the main thread"
@@ -1199,6 +1208,9 @@
          */
         private var deferredSurfaceHolder = CompletableDeferred<SurfaceHolder>()
 
+        private val pendingUpdateTime: CancellableUniqueTask =
+            CancellableUniqueTask(getUiThreadHandler())
+
         internal val mutableWatchState = getMutableWatchState().apply {
             isVisible.value = this@EngineWrapper.isVisible || forceIsVisibleForTesting()
             // Watch faces with the old [onSetBinder] init flow don't know whether the system
@@ -1353,7 +1365,7 @@
                 InteractiveInstanceManager.takePendingWallpaperInteractiveWatchFaceInstance()
 
             // In a direct boot scenario attempt to load the previously serialized parameters.
-            if (pendingWallpaperInstance == null && !isPreAndroidR()) {
+            if (pendingWallpaperInstance == null && wearSdkVersion >= Build.VERSION_CODES.R) {
                 val params = readDirectBootPrefs(_context, DIRECT_BOOT_PREFS)
                 directBootParams = params
                 // In tests a watchface may already have been created.
@@ -1692,6 +1704,7 @@
         @UiThread
         override fun onDestroy(): Unit = TraceEvent("EngineWrapper.onDestroy").use {
             super.onDestroy()
+            pendingUpdateTime.cancel()
             if (!mutableWatchState.isHeadless) {
                 mainThreadPriorityDelegate.setNormalPriority()
             }
@@ -1762,7 +1775,7 @@
         ): Bundle? {
             // From android R onwards the integration changes and no wallpaper commands are allowed
             // or expected and can/should be ignored.
-            if (!isPreAndroidR()) {
+            if (wearSdkVersion >= Build.VERSION_CODES.R) {
                 TraceEvent("onCommand Ignored").close()
                 return null
             }
@@ -1771,26 +1784,32 @@
                     uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_AMBIENT_UPDATE") {
                         ambientTickUpdate()
                     }
+
                 Constants.COMMAND_BACKGROUND_ACTION ->
                     uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_BACKGROUND_ACTION") {
                         wslFlow.onBackgroundAction(extras!!)
                     }
+
                 Constants.COMMAND_COMPLICATION_DATA ->
                     uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_COMPLICATION_DATA") {
                         wslFlow.onComplicationSlotDataUpdate(extras!!)
                     }
+
                 Constants.COMMAND_REQUEST_STYLE ->
                     uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_REQUEST_STYLE") {
                         wslFlow.onRequestStyle()
                     }
+
                 Constants.COMMAND_SET_BINDER ->
                     uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_SET_BINDER") {
                         wslFlow.onSetBinder(extras!!)
                     }
+
                 Constants.COMMAND_SET_PROPERTIES ->
                     uiThreadHandler.runOnHandlerWithTracing("onCommand COMMAND_SET_PROPERTIES") {
                         wslFlow.onPropertiesChanged(extras!!)
                     }
+
                 Constants.COMMAND_TAP ->
                     uiThreadCoroutineScope.runBlockingWithTracing("onCommand COMMAND_TAP") {
                         val watchFaceImpl = deferredWatchFaceImpl.await()
@@ -1805,6 +1824,7 @@
                             )
                         )
                     }
+
                 Constants.COMMAND_TOUCH ->
                     uiThreadCoroutineScope.runBlockingWithTracing("onCommand COMMAND_TOUCH") {
                         val watchFaceImpl = deferredWatchFaceImpl.await()
@@ -1819,6 +1839,7 @@
                             )
                         )
                     }
+
                 Constants.COMMAND_TOUCH_CANCEL ->
                     uiThreadCoroutineScope.runBlockingWithTracing(
                         "onCommand COMMAND_TOUCH_CANCEL"
@@ -1835,6 +1856,7 @@
                             )
                         )
                     }
+
                 else -> {
                 }
             }
@@ -2182,7 +2204,7 @@
                 deferredWatchFaceImpl,
                 uiThreadCoroutineScope,
                 _context.contentResolver,
-                !isPreAndroidR()
+                wearSdkVersion >= Build.VERSION_CODES.R
             )
 
             // There's no point creating BroadcastsReceiver or listening for Accessibility state
@@ -2318,7 +2340,7 @@
             // watchface after requesting a change. It used [Constants.ACTION_REQUEST_STATE] as a
             // signal to trigger the old boot flow (sending the binder etc). This is no longer
             // required from android R onwards. See (b/181965946).
-            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+            if (wearSdkVersion < Build.VERSION_CODES.R) {
                 // We are requesting state every time the watch face changes its visibility because
                 // wallpaper commands have a tendency to be dropped. By requesting it on every
                 // visibility change, we ensure that we don't become a victim of some race
@@ -2375,6 +2397,10 @@
             }
         }
 
+        override fun postInvalidate(delay: Duration) {
+            pendingUpdateTime.postDelayedUnique(delay) { invalidate() }
+        }
+
         override fun getComplicationDeniedIntent() =
             getWatchFaceImplOrNull()?.complicationDeniedDialogIntent
 
@@ -2625,7 +2651,9 @@
          * enabled.
          */
         private fun maybeSendContentDescriptionLabelsBroadcast() {
-            if (!isPreAndroidR() && getAccessibilityManager().isEnabled) {
+            if (
+                wearSdkVersion >= Build.VERSION_CODES.R && getAccessibilityManager().isEnabled
+            ) {
                 // TODO(alexclarke): This should require a permission. See http://b/184717802
                 _context.sendBroadcast(
                     Intent(Constants.ACTION_WATCH_FACE_REFRESH_A11Y_LABELS)
@@ -2688,8 +2716,11 @@
                     writer.println("WSL style init flow")
                     writer.println("watchFaceInitStarted=${wslFlow.watchFaceInitStarted}")
                 }
+
                 this.watchFaceCreatedOrPending() -> writer.println("Androidx style init flow")
-                isPreAndroidR() -> writer.println("Expecting WSL style init")
+                wearSdkVersion < Build.VERSION_CODES.R ->
+                    writer.println("Expecting WSL style init")
+
                 else -> writer.println("Expecting androidx style style init")
             }
 
@@ -2700,8 +2731,7 @@
                 )
                 if (wslFlow.iWatchFaceService.asBinder().isBinderAlive) {
                     writer.println(
-                        "iWatchFaceService.apiVersion=" +
-                            "${wslFlow.iWatchFaceService.apiVersion}"
+                        "iWatchFaceService.apiVersion=${wslFlow.iWatchFaceService.apiVersion}"
                     )
                 }
             }
@@ -2719,13 +2749,12 @@
             writer.println("frameCallbackPending=$frameCallbackPending")
             writer.println("destroyed=$destroyed")
             writer.println("surfaceDestroyed=$surfaceDestroyed")
-            writer.println(
-                "lastComplications=" + complicationsFlow.value.joinToString()
-            )
+            writer.println("lastComplications=${complicationsFlow.value.joinToString()}")
+            writer.println("pendingUpdateTime=${pendingUpdateTime.isPending()}")
 
             synchronized(lock) {
                 forEachListener("dump") {
-                    writer.println("listener = " + it.asBinder())
+                    writer.println("listener = ${it.asBinder()}")
                 }
             }
 
@@ -2743,7 +2772,7 @@
         indentingPrintWriter.println("AndroidX WatchFaceService $packageName")
         InteractiveInstanceManager.dump(indentingPrintWriter)
         EditorService.globalEditorService.dump(indentingPrintWriter)
-        if (SDK_INT >= 27) {
+        if (Build.VERSION.SDK_INT >= 27) {
             HeadlessWatchFaceImpl.dump(indentingPrintWriter)
         }
         indentingPrintWriter.flush()
diff --git a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/AsyncWatchFaceInitTest.kt b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/AsyncWatchFaceInitTest.kt
index 283dbb4..cfad995 100644
--- a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/AsyncWatchFaceInitTest.kt
+++ b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/AsyncWatchFaceInitTest.kt
@@ -112,12 +112,11 @@
         prefs: WallpaperInteractiveWatchFaceInstanceParams
     ) {
     }
-
-    override fun isPreAndroidR() = false
 }
 
-@Config(manifest = Config.NONE)
+@Config(manifest = Config.NONE, sdk = [Build.VERSION_CODES.R])
 @RunWith(WatchFaceTestRunner::class)
+@RequiresApi(Build.VERSION_CODES.R)
 public class AsyncWatchFaceInitTest {
     private val handler = mock<Handler>()
     private val surfaceHolder = mock<SurfaceHolder>()
@@ -192,10 +191,10 @@
 
     @After
     fun tearDown() {
+        InteractiveInstanceManager.releaseInstance(initParams.instanceId)
         assertThat(InteractiveInstanceManager.getInstances()).isEmpty()
     }
 
-    @RequiresApi(Build.VERSION_CODES.O_MR1)
     @Test
     public fun createInteractiveInstanceFailsIfDirectBootWatchFaceCreationIsInProgress() {
         val completableWatchFace = CompletableDeferred<WatchFace>()
@@ -236,11 +235,8 @@
         runPostedTasksFor(0)
 
         assertThat(pendingException.message).startsWith("WatchFace already exists!")
-
-        InteractiveInstanceManager.releaseInstance(initParams.instanceId)
     }
 
-    @RequiresApi(Build.VERSION_CODES.O_MR1)
     @Test
     public fun directBootAndGetExistingInstanceOrSetPendingWallpaperInteractiveWatchFaceInstance() {
         val completableDirectBootWatchFace = CompletableDeferred<WatchFace>()
diff --git a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/TestCommon.kt b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
index 67b8777..5ab6d6e 100644
--- a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
+++ b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
@@ -55,7 +55,6 @@
     private val watchState: MutableWatchState?,
     private val handler: Handler,
     private val tapListener: WatchFace.TapListener?,
-    private val preAndroidR: Boolean,
     private val directBootParams: WallpaperInteractiveWatchFaceInstanceParams?,
     private val choreographer: ChoreographerWrapper,
     var mockSystemTimeMillis: Long = 0L,
@@ -171,8 +170,6 @@
         complicationCache?.set(fileName, byteArray)
     }
 
-    override fun isPreAndroidR() = preAndroidR
-
     override fun getSystemTimeProvider() = object : SystemTimeProvider {
         override fun getSystemTimeMillis() = mockSystemTimeMillis
 
diff --git a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
index 57d6847..aae4c30 100644
--- a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
+++ b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
@@ -16,7 +16,6 @@
 
 package androidx.wear.watchface
 
-import android.annotation.SuppressLint
 import android.app.NotificationManager
 import android.app.PendingIntent
 import android.content.ComponentName
@@ -50,13 +49,12 @@
 import androidx.annotation.Px
 import androidx.annotation.RequiresApi
 import androidx.test.core.app.ApplicationProvider
-import androidx.test.filters.SdkSuppress
 import androidx.wear.watchface.complications.ComplicationSlotBounds
 import androidx.wear.watchface.complications.DefaultComplicationDataSourcePolicy
 import androidx.wear.watchface.complications.SystemDataSources
-import androidx.wear.watchface.complications.data.ComplicationPersistencePolicies
 import androidx.wear.watchface.complications.data.ComplicationDisplayPolicies
 import androidx.wear.watchface.complications.data.ComplicationExperimental
+import androidx.wear.watchface.complications.data.ComplicationPersistencePolicies
 import androidx.wear.watchface.complications.data.ComplicationType
 import androidx.wear.watchface.complications.data.CountUpTimeReference
 import androidx.wear.watchface.complications.data.EmptyComplicationData
@@ -92,9 +90,6 @@
 import androidx.wear.watchface.style.WatchFaceLayer
 import androidx.wear.watchface.style.data.UserStyleWireFormat
 import com.google.common.truth.Truth.assertThat
-import org.mockito.kotlin.eq
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.verifyNoMoreInteractions
 import java.io.StringWriter
 import java.nio.ByteBuffer
 import java.time.Instant
@@ -123,15 +118,18 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyLong
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.doAnswer
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.times
 import org.mockito.Mockito.validateMockitoUsage
 import org.mockito.Mockito.verify
-import org.robolectric.annotation.Config
+import org.mockito.Mockito.`when`
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verifyNoMoreInteractions
 import org.robolectric.Shadows.shadowOf
+import org.robolectric.annotation.Config
 
 private const val INTERACTIVE_UPDATE_RATE_MS = 16L
 private const val LEFT_COMPLICATION_ID = 1000
@@ -547,7 +545,6 @@
             watchState,
             handler,
             tapListener,
-            true,
             null,
             choreographer
         )
@@ -613,7 +610,6 @@
         userStyleSchema: UserStyleSchema,
         wallpaperInteractiveWatchFaceInstanceParams: WallpaperInteractiveWatchFaceInstanceParams,
         complicationCache: MutableMap<String, ByteArray>? = null,
-        preAndroidR: Boolean = false
     ) {
         testWatchFaceService = TestWatchFaceService(
             watchFaceType,
@@ -631,7 +627,6 @@
             watchState,
             handler,
             null,
-            preAndroidR,
             null,
             choreographer,
             mockSystemTimeMillis = looperTimeMillis,
@@ -759,6 +754,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun maybeUpdateDrawMode_setsCorrectDrawMode() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -814,6 +810,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun onDraw_zonedDateTime_setFromSystemTime() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -828,6 +825,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun onDraw_zonedDateTime_affectedCorrectly_with2xMockTime() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -856,6 +854,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun onDraw_zonedDateTime_affectedCorrectly_withMockTimeWrapping() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -927,6 +926,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun singleTaps_correctlyDetected() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -974,6 +974,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun computeBounds_does_not_mutate_it() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1001,6 +1002,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun singleTaps_inMargins_correctlyDetected() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1066,6 +1068,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     @Suppress("DEPRECATION") // setDefaultDataSourceType
     public fun lowestIdComplicationSelectedWhenMarginsOverlap() {
         val complication100 =
@@ -1121,6 +1124,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun singleTaps_onDifferentComplications() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1147,6 +1151,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun tapCancel_after_tapDown_CancelsTap() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1161,6 +1166,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun edgeComplication_tap() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1191,6 +1197,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun edgeComplicationWithBoundingArc_tap() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1226,6 +1233,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun tapListener_tap() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1250,6 +1258,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun tapListener_tap_viaWallpaperCommand() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1300,6 +1309,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun tapListener_tapComplication() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1324,6 +1334,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun interactiveFrameRate_reducedWhenBatteryLow() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1355,6 +1366,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun interactiveFrameRate_restoreWhenPowerConnectedAfterBatteryLow() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1386,6 +1398,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun computeDelayTillNextFrame_accountsForSlowDraw() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1404,6 +1417,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun computeDelayTillNextFrame_verySlowDraw() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1422,6 +1436,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun computeDelayTillNextFrame_beginFrameTimeInTheFuture() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1442,6 +1457,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun computeDelayTillNextFrame_1000ms_update_atTopOfSecond() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1463,6 +1479,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun computeDelayTillNextFrame_frame_scheduled_at_near_perfect_time() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1484,6 +1501,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun computeDelayTillNextFrame_60000ms_update_atTopOfMinute() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1505,6 +1523,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun computeDelayTillNextFrame_60000ms_update_with_stopwatchComplication() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1542,6 +1561,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun complicationSlotsManager_getNextChangeInstant() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1596,6 +1616,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun getComplicationSlotIdAt_returnsCorrectComplications() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1614,6 +1635,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun getBackgroundComplicationSlotId_returnsNull() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1627,6 +1649,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun getBackgroundComplicationSlotId_returnsCorrectId() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1639,6 +1662,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun getStoredUserStyleNotSupported_userStyle_isPersisted() {
         // The style should get persisted in a file because this test is set up using the legacy
         // Wear 2.0 APIs.
@@ -1678,7 +1702,6 @@
             watchState,
             handler,
             null,
-            true,
             null,
             choreographer
         )
@@ -1701,62 +1724,38 @@
             )
     }
 
-    @SdkSuppress(maxSdkVersion = 29)
     @Test
-    public fun onApplyWindowInsetsBeforeR_setsChinHeight() {
-        initEngine(
-            WatchFaceType.ANALOG,
-            emptyList(),
-            UserStyleSchema(emptyList())
-        )
-        // Initially the chin size is set to zero.
-        assertThat(engineWrapper.mutableWatchState.chinHeight).isEqualTo(0)
-        // When window insets are delivered to the watch face.
-        engineWrapper.onApplyWindowInsets(getChinWindowInsetsApi25(chinHeight = 12))
-        // Then the chin size is updated.
-        assertThat(engineWrapper.mutableWatchState.chinHeight).isEqualTo(12)
-    }
-
-    @SdkSuppress(minSdkVersion = 30)
-    @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
+    @RequiresApi(Build.VERSION_CODES.R)
     public fun onApplyWindowInsetsRAndAbove_setsChinHeight() {
-        initEngine(
+        initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
             emptyList(),
-            UserStyleSchema(emptyList())
+            UserStyleSchema(emptyList()),
+            WallpaperInteractiveWatchFaceInstanceParams(
+                INTERACTIVE_INSTANCE_ID,
+                DeviceConfig(
+                    false,
+                    false,
+                    0,
+                    0
+                ),
+                WatchUiState(false, 0),
+                UserStyle(emptyMap()).toWireFormat(),
+                null,
+                null
+            )
         )
         // Initially the chin size is set to zero.
         assertThat(engineWrapper.mutableWatchState.chinHeight).isEqualTo(0)
         // When window insets are delivered to the watch face.
-        engineWrapper.onApplyWindowInsets(getChinWindowInsetsApi30(chinHeight = 12))
+        engineWrapper.onApplyWindowInsets(getChinWindowInsets(chinHeight = 12))
         // Then the chin size is updated.
         assertThat(engineWrapper.mutableWatchState.chinHeight).isEqualTo(12)
     }
 
     @Test
-    public fun onApplyWindowInsetsBeforeR_multipleCallsIgnored() {
-        initEngine(
-            WatchFaceType.ANALOG,
-            emptyList(),
-            UserStyleSchema(emptyList())
-        )
-        // Initially the chin size is set to zero.
-        assertThat(engineWrapper.mutableWatchState.chinHeight).isEqualTo(0)
-        // When window insets are delivered to the watch face.
-        engineWrapper.onApplyWindowInsets(getChinWindowInsetsApi25(chinHeight = 12))
-        // Then the chin size is updated.
-        assertThat(engineWrapper.mutableWatchState.chinHeight).isEqualTo(12)
-        // When the same window insets are delivered to the watch face again.
-        engineWrapper.onApplyWindowInsets(getChinWindowInsetsApi25(chinHeight = 12))
-        // Nothing happens.
-        assertThat(engineWrapper.mutableWatchState.chinHeight).isEqualTo(12)
-        // When different window insets are delivered to the watch face again.
-        engineWrapper.onApplyWindowInsets(getChinWindowInsetsApi25(chinHeight = 24))
-        // Nothing happens and the size is unchanged.
-        assertThat(engineWrapper.mutableWatchState.chinHeight).isEqualTo(12)
-    }
-
-    @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun initWallpaperInteractiveWatchFaceInstanceWithUserStyle() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
@@ -1794,6 +1793,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun initWallpaperInteractiveWatchFaceInstanceWithUserStyleThatDoesntMatchSchema() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
@@ -1819,6 +1819,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun wear2ImmutablePropertiesSetCorrectly() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1834,6 +1835,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun wear2ImmutablePropertiesSetCorrectly2() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1849,6 +1851,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun wallpaperInteractiveWatchFaceImmutablePropertiesSetCorrectly() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
@@ -1875,6 +1878,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun invalidOldStyleIdReplacedWithDefault() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
@@ -1894,6 +1898,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun onCreate_calls_setActiveComplications_withCorrectIDs() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1908,6 +1913,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun onCreate_calls_setContentDescriptionLabels_withCorrectArgs() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1930,6 +1936,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun onCreate_calls_setContentDescriptionLabels_withCorrectArgs_noComplications() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1948,6 +1955,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun ContentDescriptionLabels_notMadeForEmptyComplication() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -1976,6 +1984,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun moveComplications() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -2039,6 +2048,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun styleChangesAccessibilityTraversalIndex() {
         val rightAndSelectComplicationsOption = ComplicationSlotsOption(
             Option.Id(RIGHT_AND_LEFT_COMPLICATIONS),
@@ -2153,6 +2163,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun centerX_and_centerY_containUpToDateValues() {
         initEngine(WatchFaceType.ANALOG, emptyList(), UserStyleSchema(emptyList()))
 
@@ -2175,6 +2186,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun requestStyleBeforeSetBinder() {
         testWatchFaceService = TestWatchFaceService(
             WatchFaceType.ANALOG,
@@ -2191,7 +2203,6 @@
             watchState,
             handler,
             null,
-            true,
             null,
             choreographer
         )
@@ -2214,6 +2225,7 @@
 
     @Suppress("DEPRECATION") // setDefaultDataSourceType
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun defaultComplicationDataSourcesWithFallbacks_newApi() {
         val dataSource1 = ComponentName("com.app1", "com.app1.App1")
         val dataSource2 = ComponentName("com.app2", "com.app2.App2")
@@ -2245,6 +2257,7 @@
 
     @Suppress("DEPRECATION") // setDefaultDataSourceType
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun defaultComplicationDataSourcesWithFallbacks_oldApi() {
         val dataSource1 = ComponentName("com.app1", "com.app1.App1")
         val dataSource2 = ComponentName("com.app2", "com.app2.App2")
@@ -2285,6 +2298,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun previewReferenceTimeMillisAnalog() {
         val instanceParams = WallpaperInteractiveWatchFaceInstanceParams(
             INTERACTIVE_INSTANCE_ID,
@@ -2316,6 +2330,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun previewReferenceTimeMillisDigital() {
         val instanceParams = WallpaperInteractiveWatchFaceInstanceParams(
             INTERACTIVE_INSTANCE_ID,
@@ -2347,6 +2362,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun getComplicationDetails() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -2404,6 +2420,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun getComplicationDetails_early_init_with_styleOverrides() {
         val complicationsStyleSetting = ComplicationSlotsUserStyleSetting(
             UserStyleSetting.Id("complications_style_setting"),
@@ -2437,7 +2454,6 @@
             watchState,
             handler,
             null,
-            false,
             null,
             choreographer
         )
@@ -2493,6 +2509,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun shouldAnimateOverrideControlsEnteringAmbientMode() {
         lateinit var testRenderer: TestRendererWithShouldAnimate
         testWatchFaceService = TestWatchFaceService(
@@ -2511,7 +2528,6 @@
             watchState,
             handler,
             null,
-            true,
             null,
             choreographer
         )
@@ -2538,6 +2554,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun complicationsUserStyleSettingSelectionAppliesChanges() {
         initEngine(
             WatchFaceType.DIGITAL,
@@ -2581,6 +2598,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun partialComplicationOverrides() {
         initEngine(
             WatchFaceType.DIGITAL,
@@ -2612,8 +2630,10 @@
         assertFalse(leftComplication.enabled)
         assertTrue(rightComplication.enabled)
         assertEquals(rightComplication.nameResourceId, NAME_RESOURCE_ID)
-        assertEquals(rightComplication.screenReaderNameResourceId,
-            SCREEN_READER_NAME_RESOURCE_ID)
+        assertEquals(
+            rightComplication.screenReaderNameResourceId,
+            SCREEN_READER_NAME_RESOURCE_ID
+        )
 
         // Select both complicationSlots.
         val mutableUserStyleC = currentUserStyleRepository.userStyle.value.toMutableUserStyle()
@@ -2627,6 +2647,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun partialComplicationOverrideAppliedToInitialStyle() {
         val complicationsStyleSetting = ComplicationSlotsUserStyleSetting(
             UserStyleSetting.Id("complications_style_setting"),
@@ -2731,11 +2752,8 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.TIRAMISU])
     fun hierarchical_complicationsStyleSetting() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
-            return
-        }
-
         val option1 = ListUserStyleSetting.ListOption(
             Option.Id("1"),
             displayName = "1",
@@ -2765,11 +2783,23 @@
             WatchFaceLayer.ALL_WATCH_FACE_LAYERS
         )
 
-        initEngine(
+        initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.DIGITAL,
             listOf(leftComplication, rightComplication),
             UserStyleSchema(listOf(choice, complicationsStyleSetting, complicationsStyleSetting2)),
-            apiVersion = 4
+            WallpaperInteractiveWatchFaceInstanceParams(
+                INTERACTIVE_INSTANCE_ID,
+                DeviceConfig(
+                    false,
+                    false,
+                    0,
+                    0
+                ),
+                WatchUiState(false, 0),
+                UserStyle(emptyMap()).toWireFormat(),
+                null,
+                null
+            )
         )
 
         currentUserStyleRepository.updateUserStyle(
@@ -2811,6 +2841,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun observeComplicationData() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
@@ -2872,6 +2903,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun complicationCache() {
         val complicationCache = HashMap<String, ByteArray>()
         val instanceParams = WallpaperInteractiveWatchFaceInstanceParams(
@@ -2942,7 +2974,6 @@
             watchState,
             handler,
             null,
-            true,
             null,
             choreographer,
             complicationCache = complicationCache
@@ -3003,7 +3034,7 @@
     }
 
     @Test
-    @Suppress("NewApi")
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun complicationCachePolicy() {
         val complicationCache = HashMap<String, ByteArray>()
         val instanceParams = WallpaperInteractiveWatchFaceInstanceParams(
@@ -3069,7 +3100,6 @@
             watchState,
             handler,
             null,
-            true,
             null,
             choreographer,
             complicationCache = complicationCache
@@ -3127,6 +3157,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun complicationCache_timeline() {
         val complicationCache = HashMap<String, ByteArray>()
         val instanceParams = WallpaperInteractiveWatchFaceInstanceParams(
@@ -3186,7 +3217,6 @@
             watchState,
             handler,
             null,
-            true,
             null,
             choreographer,
             complicationCache = complicationCache
@@ -3255,6 +3285,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun complicationsInitialized_with_NoComplicationComplicationData() {
         initEngine(
             WatchFaceType.DIGITAL,
@@ -3271,8 +3302,9 @@
         ).isInstanceOf(NoDataComplicationData::class.java)
     }
 
-    @RequiresApi(Build.VERSION_CODES.O_MR1)
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
+    @RequiresApi(Build.VERSION_CODES.O_MR1)
     public fun headless_complicationsInitialized_with_EmptyComplicationData() {
         testWatchFaceService = TestWatchFaceService(
             WatchFaceType.ANALOG,
@@ -3289,7 +3321,6 @@
             watchState,
             handler,
             null,
-            false, // Allows DirectBoot
             WallpaperInteractiveWatchFaceInstanceParams(
                 "Headless",
                 DeviceConfig(
@@ -3339,6 +3370,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun complication_isActiveAt() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
@@ -3406,6 +3438,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun updateInvalidCompliationIdDoesNotCrash() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
@@ -3453,6 +3486,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun watchStateStateFlowDataMembersHaveValues() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
@@ -3480,6 +3514,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun isBatteryLowAndNotCharging_modified_by_broadcasts() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
@@ -3515,6 +3550,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun processBatteryStatus() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
@@ -3570,6 +3606,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun isAmbientInitalisedEvenWithoutPropertiesSent() {
         testWatchFaceService = TestWatchFaceService(
             WatchFaceType.ANALOG,
@@ -3586,7 +3623,6 @@
             watchState,
             handler,
             null,
-            true,
             null,
             choreographer
         )
@@ -3602,6 +3638,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun ambientToInteractiveTransition() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
@@ -3647,6 +3684,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun interactiveToAmbientTransition() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
@@ -3685,6 +3723,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun onDestroy_clearsInstanceRecord() {
         val instanceId = INTERACTIVE_INSTANCE_ID
         initWallpaperInteractiveWatchFaceInstance(
@@ -3711,6 +3750,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun sendComplicationWallpaperCommandPreRFlow() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -3737,6 +3777,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun sendComplicationWallpaperCommandIgnoredPostRFlow() {
         val instanceId = INTERACTIVE_INSTANCE_ID
         initWallpaperInteractiveWatchFaceInstance(
@@ -3785,6 +3826,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun directBoot() {
         val instanceId = "DirectBootInstance"
         testWatchFaceService = TestWatchFaceService(
@@ -3802,7 +3844,6 @@
             watchState,
             handler,
             null,
-            false, // Allows DirectBoot
             WallpaperInteractiveWatchFaceInstanceParams(
                 instanceId,
                 DeviceConfig(
@@ -3840,6 +3881,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun headlessFlagPreventsDirectBoot() {
         val instanceId = "DirectBootInstance"
         testWatchFaceService = TestWatchFaceService(
@@ -3857,7 +3899,6 @@
             watchState,
             handler,
             null,
-            false, // Allows DirectBoot
             WallpaperInteractiveWatchFaceInstanceParams(
                 instanceId,
                 DeviceConfig(
@@ -3888,6 +3929,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun firstOnVisibilityChangedIgnoredPostRFlow() {
         val instanceId = INTERACTIVE_INSTANCE_ID
         initWallpaperInteractiveWatchFaceInstance(
@@ -3941,6 +3983,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun complicationsUserStyleSetting_with_setComplicationBounds() {
         val rightComplicationBoundsOption = ComplicationSlotsOption(
             Option.Id(RIGHT_COMPLICATION),
@@ -4019,6 +4062,7 @@
 
     @Suppress("DEPRECATION") // DefaultComplicationDataSourcePolicyAndType
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun canvasComplication_onRendererCreated() {
         val leftCanvasComplication = mock<CanvasComplication>()
         val leftComplication =
@@ -4058,6 +4102,7 @@
 
     @Suppress("DEPRECATION") // DefaultComplicationDataSourcePolicyAndType
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun complicationSlotsWithTheSameRenderer() {
         val sameCanvasComplication = mock<CanvasComplication>()
         val leftComplication =
@@ -4097,6 +4142,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun additionalContentDescriptionLabelsSetBeforeWatchFaceInitComplete() {
         val pendingIntent = PendingIntent.getActivity(
             context, 0, Intent("Example"),
@@ -4131,7 +4177,6 @@
             watchState,
             handler,
             null,
-            false,
             null,
             choreographer
         )
@@ -4187,6 +4232,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun onAccessibilityStateChanged_preAndroidR() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
@@ -4205,7 +4251,6 @@
                 emptyList(),
                 null
             ),
-            preAndroidR = true
         )
 
         engineWrapper.systemViewOfContentDescriptionLabelsIsStale = true
@@ -4218,6 +4263,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun onAccessibilityStateChanged_androidR_or_above() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
@@ -4236,7 +4282,6 @@
                 emptyList(),
                 null
             ),
-            preAndroidR = false
         )
 
         engineWrapper.systemViewOfContentDescriptionLabelsIsStale = true
@@ -4250,6 +4295,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun contentDescriptionLabels_contains_ComplicationData() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
@@ -4318,6 +4364,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun schemaWithTooLargeIcon() {
         val tooLargeIcon = Icon.createWithBitmap(
             Bitmap.createBitmap(
@@ -4371,6 +4418,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun schemaWithTooLargeWireFormat() {
         val longOptionsList = ArrayList<ListUserStyleSetting.ListOption>()
         for (i in 0..10000) {
@@ -4417,6 +4465,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun getComplicationSlotMetadataWireFormats_parcelTest() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
@@ -4463,6 +4512,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     @RequiresApi(Build.VERSION_CODES.O_MR1)
     public fun glesRendererLifecycle() {
         val eventLog = ArrayList<String>()
@@ -4515,7 +4565,6 @@
             null,
             handler,
             null,
-            false,
             null,
             choreographer,
             forceIsVisible = true
@@ -4591,6 +4640,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     @RequiresApi(Build.VERSION_CODES.O_MR1)
     public fun assetLifeCycle_CanvasRenderer() {
         val eventLog = ArrayList<String>()
@@ -4654,7 +4704,6 @@
             null,
             handler,
             null,
-            false,
             null,
             choreographer,
             forceIsVisible = true
@@ -4728,6 +4777,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     @RequiresApi(Build.VERSION_CODES.O_MR1)
     public fun assetLifeCycle_GlesRenderer() {
         val eventLog = ArrayList<String>()
@@ -4785,7 +4835,6 @@
             null,
             handler,
             null,
-            false,
             null,
             choreographer,
             forceIsVisible = true
@@ -4859,6 +4908,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun renderer_onDestroy_called_even_if_init_not_complete() {
         val initDeferred = CompletableDeferred<Unit>()
         var onDestroyCalled = false
@@ -4889,7 +4939,6 @@
             watchState,
             handler,
             null,
-            false,
             null,
             choreographer
         )
@@ -4939,6 +4988,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun dump_androidXFlow() {
         // Advance time a little so timestamps are not zero
         looperTimeMillis = 1000
@@ -4995,6 +5045,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun dump_wslFlow() {
         initEngine(
             WatchFaceType.DIGITAL,
@@ -5012,6 +5063,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun uiThreadPriority_interactive() {
         testWatchFaceService = TestWatchFaceService(
             WatchFaceType.DIGITAL,
@@ -5028,7 +5080,6 @@
             watchState,
             handler,
             null,
-            false,
             null,
             choreographer,
             mainThreadPriorityDelegate = mainThreadPriorityDelegate
@@ -5090,8 +5141,9 @@
         assertThat(mainThreadPriorityDelegate.priority).isEqualTo(Priority.Normal)
     }
 
-    @RequiresApi(Build.VERSION_CODES.O_MR1)
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
+    @RequiresApi(Build.VERSION_CODES.O_MR1)
     public fun uiThreadPriority_headless() {
         testWatchFaceService = TestWatchFaceService(
             WatchFaceType.ANALOG,
@@ -5108,7 +5160,6 @@
             watchState,
             handler,
             null,
-            false, // Allows DirectBoot
             WallpaperInteractiveWatchFaceInstanceParams(
                 "Headless",
                 DeviceConfig(
@@ -5147,6 +5198,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     fun onVisibilityChanged_true_always_renders_a_frame() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -5165,6 +5217,8 @@
         assertThat(renderer.lastOnDrawZonedDateTime!!.toInstant().toEpochMilli()).isEqualTo(2000L)
     }
 
+    @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     @RequiresApi(Build.VERSION_CODES.O_MR1)
     public fun headlessId() {
         testWatchFaceService = TestWatchFaceService(
@@ -5182,9 +5236,8 @@
             watchState,
             handler,
             null,
-            false, // Allows DirectBoot
             WallpaperInteractiveWatchFaceInstanceParams(
-                "Headless",
+                "wfId-Headless",
                 DeviceConfig(
                     false,
                     false,
@@ -5208,13 +5261,14 @@
                 DeviceConfig(false, false, 100, 200),
                 100,
                 100,
-                "Headless-instance"
+                "wfId-Headless-instance"
             )
         )
-        assertThat(watchState.watchFaceInstanceId.value).isEqualTo("Headless-instance")
+        assertThat(watchState.watchFaceInstanceId.value).isEqualTo("wfId-Headless-instance")
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun selectComplicationDataForInstant_overlapping() {
         val a = ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
             .setShortText(ComplicationText.plainText("A"))
@@ -5269,6 +5323,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun selectComplicationDataForInstant_disjoint() {
         val a = ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
             .setShortText(ComplicationText.plainText("A"))
@@ -5323,6 +5378,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun selectComplicationDataForInstant_timeLineWithPlaceholder() {
         val placeholderText =
             androidx.wear.watchface.complications.data.ComplicationText.PLACEHOLDER
@@ -5373,6 +5429,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun renderParameters_isScreenshot() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
@@ -5413,6 +5470,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     @Suppress("Deprecation")
     public fun applyComplicationSlotsStyleCategoryOption() {
         initWallpaperInteractiveWatchFaceInstance(
@@ -5526,6 +5584,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun onActionScreenOff_preR() {
         Settings.Global.putInt(
             context.contentResolver,
@@ -5548,7 +5607,6 @@
             watchState,
             handler,
             null,
-            true,
             null,
             choreographer
         )
@@ -5597,6 +5655,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun onActionScreenOff_ambientNotEnabled() {
         Settings.Global.putInt(
             context.contentResolver,
@@ -5619,7 +5678,6 @@
             watchState,
             handler,
             null,
-            false,
             null,
             choreographer
         )
@@ -5668,6 +5726,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun onActionScreenOff_onActionScreenOn_ambientEnabled() {
         Settings.Global.putInt(
             context.contentResolver,
@@ -5690,7 +5749,6 @@
             watchState,
             handler,
             null,
-            false,
             null,
             choreographer
         )
@@ -5752,6 +5810,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun onActionTimeTick() {
         testWatchFaceService = TestWatchFaceService(
             WatchFaceType.DIGITAL,
@@ -5768,7 +5827,6 @@
             watchState,
             handler,
             null,
-            false,
             null,
             choreographer
         )
@@ -5830,6 +5888,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.O_MR1])
     public fun setComplicationDataListMergesCorrectly() {
         initEngine(
             WatchFaceType.ANALOG,
@@ -5872,6 +5931,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun updateInstance() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
@@ -5916,6 +5976,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun updateComplications_after_updateInstance() {
         val complicationList = listOf(
             IdAndComplicationDataWireFormat(
@@ -5960,21 +6021,25 @@
         }
 
         assertThat(leftComplication.complicationData.value).isInstanceOf(
-            NoDataComplicationData::class.java)
+            NoDataComplicationData::class.java
+        )
         assertThat(rightComplication.complicationData.value).isInstanceOf(
-            NoDataComplicationData::class.java)
+            NoDataComplicationData::class.java
+        )
 
         interactiveWatchFaceInstance.updateComplicationData(complicationList)
 
         assertThat(leftComplication.complicationData.value).isInstanceOf(
-            LongTextComplicationData::class.java)
+            LongTextComplicationData::class.java
+        )
         assertThat(rightComplication.complicationData.value).isInstanceOf(
-            ShortTextComplicationData::class.java)
+            ShortTextComplicationData::class.java
+        )
     }
 
     @OptIn(WatchFaceExperimental::class)
     @Test
-    @RequiresApi(27)
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun onComputeColors() {
         @Suppress("DEPRECATION")
         lateinit var renderer: Renderer.CanvasRenderer
@@ -5999,7 +6064,8 @@
                         canvas: Canvas,
                         bounds: Rect,
                         zonedDateTime: ZonedDateTime
-                    ) { }
+                    ) {
+                    }
 
                     override fun renderHighlightLayer(
                         canvas: Canvas,
@@ -6014,7 +6080,6 @@
             null,
             handler,
             null,
-            false,
             null,
             choreographer,
             forceIsVisible = true
@@ -6057,7 +6122,7 @@
         val listener = object : IWatchfaceListener.Stub() {
             override fun getApiVersion() = 1
 
-            override fun onWatchfaceReady() { }
+            override fun onWatchfaceReady() {}
 
             override fun onWatchfaceColorsChanged(watchFaceColors: WatchFaceColorsWireFormat?) {
                 lastWatchFaceColors = watchFaceColors?.toApiFormat()
@@ -6098,6 +6163,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun onPreviewImageUpdateRequested() {
         @Suppress("DEPRECATION")
         lateinit var renderer: Renderer.CanvasRenderer
@@ -6117,7 +6183,8 @@
                         canvas: Canvas,
                         bounds: Rect,
                         zonedDateTime: ZonedDateTime
-                    ) { }
+                    ) {
+                    }
 
                     override fun renderHighlightLayer(
                         canvas: Canvas,
@@ -6132,7 +6199,6 @@
             null,
             handler,
             null,
-            false,
             null,
             choreographer,
             forceIsVisible = true
@@ -6175,7 +6241,7 @@
         val listener = object : IWatchfaceListener.Stub() {
             override fun getApiVersion() = 1
 
-            override fun onWatchfaceReady() { }
+            override fun onWatchfaceReady() {}
 
             override fun onWatchfaceColorsChanged(watchFaceColors: WatchFaceColorsWireFormat?) {}
 
@@ -6217,6 +6283,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     public fun onPreviewImageUpdateRequested_earlyCall() {
         @Suppress("DEPRECATION")
         lateinit var renderer: Renderer.CanvasRenderer
@@ -6256,7 +6323,6 @@
             null,
             handler,
             null,
-            false,
             null,
             choreographer,
             forceIsVisible = true
@@ -6322,6 +6388,7 @@
     }
 
     @Test
+    @Config(sdk = [Build.VERSION_CODES.R])
     @RequiresApi(Build.VERSION_CODES.O_MR1)
     public fun sendPreviewImageNeedsUpdateRequest_headlessInstance() {
         @Suppress("DEPRECATION")
@@ -6341,11 +6408,13 @@
                     init {
                         sendPreviewImageNeedsUpdateRequest()
                     }
+
                     override fun render(
                         canvas: Canvas,
                         bounds: Rect,
                         zonedDateTime: ZonedDateTime
-                    ) { }
+                    ) {
+                    }
 
                     override fun renderHighlightLayer(
                         canvas: Canvas,
@@ -6360,7 +6429,6 @@
             null,
             handler,
             null,
-            false,
             null,
             choreographer,
             forceIsVisible = true
@@ -6388,7 +6456,8 @@
     }
 
     @Test
-    @Suppress("NewApi")
+    @Config(sdk = [Build.VERSION_CODES.R])
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     public fun doNotDisplayComplicationWhenScreenLocked() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
@@ -6463,15 +6532,8 @@
         )
     }
 
-    @SuppressLint("NewApi")
-    @Suppress("DEPRECATION")
-    private fun getChinWindowInsetsApi25(@Px chinHeight: Int): WindowInsets =
-        WindowInsets.Builder().setSystemWindowInsets(
-            Insets.of(0, 0, 0, chinHeight)
-        ).build()
-
-    @SuppressLint("NewApi")
-    private fun getChinWindowInsetsApi30(@Px chinHeight: Int): WindowInsets =
+    @RequiresApi(Build.VERSION_CODES.R)
+    private fun getChinWindowInsets(@Px chinHeight: Int): WindowInsets =
         WindowInsets.Builder().setInsets(
             WindowInsets.Type.systemBars(),
             Insets.of(Rect().apply { bottom = chinHeight })
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ImageDragActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ImageDragActivity.java
index 1ae07c2..1569ff6 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ImageDragActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ImageDragActivity.java
@@ -18,6 +18,7 @@
 
 import android.os.Bundle;
 import android.webkit.WebView;
+import android.webkit.WebViewClient;
 
 import androidx.annotation.Nullable;
 import androidx.appcompat.app.AppCompatActivity;
@@ -33,6 +34,7 @@
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_image_drag);
         WebView demoWebview = findViewById(R.id.image_webview);
+        demoWebview.setWebViewClient(new WebViewClient()); // Open links in this WebView.
 
         demoWebview.loadUrl("www.google.com");
     }
diff --git a/webkit/webkit/api/current.txt b/webkit/webkit/api/current.txt
index faf13cb..0c9d5ee 100644
--- a/webkit/webkit/api/current.txt
+++ b/webkit/webkit/api/current.txt
@@ -5,6 +5,16 @@
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_COOKIE_INFO, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static java.util.List<java.lang.String!> getCookieInfo(android.webkit.CookieManager, String);
   }
 
+  public final class DropDataContentProvider extends android.content.ContentProvider {
+    ctor public DropDataContentProvider();
+    method public int delete(android.net.Uri, String?, String![]?);
+    method public String? getType(android.net.Uri);
+    method public android.net.Uri? insert(android.net.Uri, android.content.ContentValues?);
+    method public boolean onCreate();
+    method public android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?);
+    method public int update(android.net.Uri, android.content.ContentValues?, String?, String![]?);
+  }
+
   public abstract class JavaScriptReplyProxy {
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void postMessage(String);
   }
diff --git a/webkit/webkit/api/public_plus_experimental_current.txt b/webkit/webkit/api/public_plus_experimental_current.txt
index faf13cb..0c9d5ee 100644
--- a/webkit/webkit/api/public_plus_experimental_current.txt
+++ b/webkit/webkit/api/public_plus_experimental_current.txt
@@ -5,6 +5,16 @@
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_COOKIE_INFO, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static java.util.List<java.lang.String!> getCookieInfo(android.webkit.CookieManager, String);
   }
 
+  public final class DropDataContentProvider extends android.content.ContentProvider {
+    ctor public DropDataContentProvider();
+    method public int delete(android.net.Uri, String?, String![]?);
+    method public String? getType(android.net.Uri);
+    method public android.net.Uri? insert(android.net.Uri, android.content.ContentValues?);
+    method public boolean onCreate();
+    method public android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?);
+    method public int update(android.net.Uri, android.content.ContentValues?, String?, String![]?);
+  }
+
   public abstract class JavaScriptReplyProxy {
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void postMessage(String);
   }
diff --git a/webkit/webkit/api/restricted_current.txt b/webkit/webkit/api/restricted_current.txt
index faf13cb..0c9d5ee 100644
--- a/webkit/webkit/api/restricted_current.txt
+++ b/webkit/webkit/api/restricted_current.txt
@@ -5,6 +5,16 @@
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_COOKIE_INFO, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static java.util.List<java.lang.String!> getCookieInfo(android.webkit.CookieManager, String);
   }
 
+  public final class DropDataContentProvider extends android.content.ContentProvider {
+    ctor public DropDataContentProvider();
+    method public int delete(android.net.Uri, String?, String![]?);
+    method public String? getType(android.net.Uri);
+    method public android.net.Uri? insert(android.net.Uri, android.content.ContentValues?);
+    method public boolean onCreate();
+    method public android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?);
+    method public int update(android.net.Uri, android.content.ContentValues?, String?, String![]?);
+  }
+
   public abstract class JavaScriptReplyProxy {
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void postMessage(String);
   }
diff --git a/webkit/webkit/src/main/java/androidx/webkit/DropDataContentProvider.java b/webkit/webkit/src/main/java/androidx/webkit/DropDataContentProvider.java
index 6c38760..6af699c 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/DropDataContentProvider.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/DropDataContentProvider.java
@@ -25,7 +25,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
 import androidx.webkit.internal.WebViewGlueCommunicator;
 
 import org.chromium.support_lib_boundary.DropDataContentProviderBoundaryInterface;
@@ -33,13 +32,23 @@
 import java.io.FileNotFoundException;
 
 /**
- * TODO(1353048): Un-hide this after finishing the feature.
+ * WebView provides partial support for Android
+ * <a href="https://developer.android.com/develop/ui/views/touch-and-input/drag-drop">
+ * Drag and Drop</a> allowing images, text and links to be dragged out of a WebView.
  *
- * @hide
- * This should be added to the manifest in order to enable dragging images out.
+ * The content provider is required to make the images drag work, to enable, you should add this
+ * class to your manifest, for example:
+ *
+ * <pre class="prettyprint">
+ *  &lt;provider
+ *             android:authorities="{{your package}}.DropDataProvider"
+ *             android:name="androidx.webkit.DropDataContentProvider"
+ *             android:exported="false"
+ *             android:grantUriPermissions="true"/&gt;
+ * </pre>
+ *
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
-public class DropDataContentProvider extends ContentProvider {
+public final class DropDataContentProvider extends ContentProvider {
     DropDataContentProviderBoundaryInterface mImpl;
 
     @Override
diff --git a/window/window/api/current.txt b/window/window/api/current.txt
index f0f8603..4c43822 100644
--- a/window/window/api/current.txt
+++ b/window/window/api/current.txt
@@ -50,6 +50,19 @@
     property public final boolean isEmpty;
   }
 
+  public final class EmbeddingAspectRatio {
+    method public static androidx.window.embedding.EmbeddingAspectRatio alwaysAllow();
+    method public static androidx.window.embedding.EmbeddingAspectRatio alwaysDisallow();
+    method public static androidx.window.embedding.EmbeddingAspectRatio ratio(@FloatRange(from=1.0, fromInclusive=false) float ratio);
+    field public static final androidx.window.embedding.EmbeddingAspectRatio.Companion Companion;
+  }
+
+  public static final class EmbeddingAspectRatio.Companion {
+    method public androidx.window.embedding.EmbeddingAspectRatio alwaysAllow();
+    method public androidx.window.embedding.EmbeddingAspectRatio alwaysDisallow();
+    method public androidx.window.embedding.EmbeddingAspectRatio ratio(@FloatRange(from=1.0, fromInclusive=false) float ratio);
+  }
+
   public abstract class EmbeddingRule {
   }
 
@@ -121,6 +134,8 @@
     method public androidx.window.embedding.SplitPairRule.Builder setFinishPrimaryWithSecondary(int finishPrimaryWithSecondary);
     method public androidx.window.embedding.SplitPairRule.Builder setFinishSecondaryWithPrimary(int finishSecondaryWithPrimary);
     method public androidx.window.embedding.SplitPairRule.Builder setLayoutDirection(int layoutDirection);
+    method public androidx.window.embedding.SplitPairRule.Builder setMaxAspectRatioInLandscape(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
+    method public androidx.window.embedding.SplitPairRule.Builder setMaxAspectRatioInPortrait(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
     method public androidx.window.embedding.SplitPairRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
     method public androidx.window.embedding.SplitPairRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
     method public androidx.window.embedding.SplitPairRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
@@ -142,6 +157,8 @@
     method public androidx.window.embedding.SplitPlaceholderRule build();
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithPlaceholder(int finishPrimaryWithPlaceholder);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setLayoutDirection(int layoutDirection);
+    method public androidx.window.embedding.SplitPlaceholderRule.Builder setMaxAspectRatioInLandscape(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
+    method public androidx.window.embedding.SplitPlaceholderRule.Builder setMaxAspectRatioInPortrait(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
@@ -150,18 +167,25 @@
 
   public class SplitRule extends androidx.window.embedding.EmbeddingRule {
     method public final int getLayoutDirection();
+    method public final androidx.window.embedding.EmbeddingAspectRatio getMaxAspectRatioInLandscape();
+    method public final androidx.window.embedding.EmbeddingAspectRatio getMaxAspectRatioInPortrait();
     method public final int getMinSmallestWidthDp();
     method public final int getMinWidthDp();
     method public final float getSplitRatio();
     property public final int layoutDirection;
+    property public final androidx.window.embedding.EmbeddingAspectRatio maxAspectRatioInLandscape;
+    property public final androidx.window.embedding.EmbeddingAspectRatio maxAspectRatioInPortrait;
     property public final int minSmallestWidthDp;
     property public final int minWidthDp;
     property public final float splitRatio;
     field public static final androidx.window.embedding.SplitRule.Companion Companion;
-    field public static final int DEFAULT_SPLIT_MIN_DIMENSION_DP = 600; // 0x258
     field public static final int FINISH_ADJACENT = 2; // 0x2
     field public static final int FINISH_ALWAYS = 1; // 0x1
     field public static final int FINISH_NEVER = 0; // 0x0
+    field public static final androidx.window.embedding.EmbeddingAspectRatio SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT;
+    field public static final androidx.window.embedding.EmbeddingAspectRatio SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT;
+    field public static final int SPLIT_MIN_DIMENSION_ALWAYS_ALLOW = 0; // 0x0
+    field public static final int SPLIT_MIN_DIMENSION_DP_DEFAULT = 600; // 0x258
   }
 
   public static final class SplitRule.Companion {
diff --git a/window/window/api/public_plus_experimental_current.txt b/window/window/api/public_plus_experimental_current.txt
index f4e24a6..da9c818 100644
--- a/window/window/api/public_plus_experimental_current.txt
+++ b/window/window/api/public_plus_experimental_current.txt
@@ -57,6 +57,19 @@
     property public final boolean isEmpty;
   }
 
+  public final class EmbeddingAspectRatio {
+    method public static androidx.window.embedding.EmbeddingAspectRatio alwaysAllow();
+    method public static androidx.window.embedding.EmbeddingAspectRatio alwaysDisallow();
+    method public static androidx.window.embedding.EmbeddingAspectRatio ratio(@FloatRange(from=1.0, fromInclusive=false) float ratio);
+    field public static final androidx.window.embedding.EmbeddingAspectRatio.Companion Companion;
+  }
+
+  public static final class EmbeddingAspectRatio.Companion {
+    method public androidx.window.embedding.EmbeddingAspectRatio alwaysAllow();
+    method public androidx.window.embedding.EmbeddingAspectRatio alwaysDisallow();
+    method public androidx.window.embedding.EmbeddingAspectRatio ratio(@FloatRange(from=1.0, fromInclusive=false) float ratio);
+  }
+
   public abstract class EmbeddingRule {
   }
 
@@ -128,6 +141,8 @@
     method public androidx.window.embedding.SplitPairRule.Builder setFinishPrimaryWithSecondary(int finishPrimaryWithSecondary);
     method public androidx.window.embedding.SplitPairRule.Builder setFinishSecondaryWithPrimary(int finishSecondaryWithPrimary);
     method public androidx.window.embedding.SplitPairRule.Builder setLayoutDirection(int layoutDirection);
+    method public androidx.window.embedding.SplitPairRule.Builder setMaxAspectRatioInLandscape(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
+    method public androidx.window.embedding.SplitPairRule.Builder setMaxAspectRatioInPortrait(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
     method public androidx.window.embedding.SplitPairRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
     method public androidx.window.embedding.SplitPairRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
     method public androidx.window.embedding.SplitPairRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
@@ -149,6 +164,8 @@
     method public androidx.window.embedding.SplitPlaceholderRule build();
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithPlaceholder(int finishPrimaryWithPlaceholder);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setLayoutDirection(int layoutDirection);
+    method public androidx.window.embedding.SplitPlaceholderRule.Builder setMaxAspectRatioInLandscape(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
+    method public androidx.window.embedding.SplitPlaceholderRule.Builder setMaxAspectRatioInPortrait(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
@@ -157,18 +174,25 @@
 
   public class SplitRule extends androidx.window.embedding.EmbeddingRule {
     method public final int getLayoutDirection();
+    method public final androidx.window.embedding.EmbeddingAspectRatio getMaxAspectRatioInLandscape();
+    method public final androidx.window.embedding.EmbeddingAspectRatio getMaxAspectRatioInPortrait();
     method public final int getMinSmallestWidthDp();
     method public final int getMinWidthDp();
     method public final float getSplitRatio();
     property public final int layoutDirection;
+    property public final androidx.window.embedding.EmbeddingAspectRatio maxAspectRatioInLandscape;
+    property public final androidx.window.embedding.EmbeddingAspectRatio maxAspectRatioInPortrait;
     property public final int minSmallestWidthDp;
     property public final int minWidthDp;
     property public final float splitRatio;
     field public static final androidx.window.embedding.SplitRule.Companion Companion;
-    field public static final int DEFAULT_SPLIT_MIN_DIMENSION_DP = 600; // 0x258
     field public static final int FINISH_ADJACENT = 2; // 0x2
     field public static final int FINISH_ALWAYS = 1; // 0x1
     field public static final int FINISH_NEVER = 0; // 0x0
+    field public static final androidx.window.embedding.EmbeddingAspectRatio SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT;
+    field public static final androidx.window.embedding.EmbeddingAspectRatio SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT;
+    field public static final int SPLIT_MIN_DIMENSION_ALWAYS_ALLOW = 0; // 0x0
+    field public static final int SPLIT_MIN_DIMENSION_DP_DEFAULT = 600; // 0x258
   }
 
   public static final class SplitRule.Companion {
diff --git a/window/window/api/restricted_current.txt b/window/window/api/restricted_current.txt
index f0f8603..4c43822 100644
--- a/window/window/api/restricted_current.txt
+++ b/window/window/api/restricted_current.txt
@@ -50,6 +50,19 @@
     property public final boolean isEmpty;
   }
 
+  public final class EmbeddingAspectRatio {
+    method public static androidx.window.embedding.EmbeddingAspectRatio alwaysAllow();
+    method public static androidx.window.embedding.EmbeddingAspectRatio alwaysDisallow();
+    method public static androidx.window.embedding.EmbeddingAspectRatio ratio(@FloatRange(from=1.0, fromInclusive=false) float ratio);
+    field public static final androidx.window.embedding.EmbeddingAspectRatio.Companion Companion;
+  }
+
+  public static final class EmbeddingAspectRatio.Companion {
+    method public androidx.window.embedding.EmbeddingAspectRatio alwaysAllow();
+    method public androidx.window.embedding.EmbeddingAspectRatio alwaysDisallow();
+    method public androidx.window.embedding.EmbeddingAspectRatio ratio(@FloatRange(from=1.0, fromInclusive=false) float ratio);
+  }
+
   public abstract class EmbeddingRule {
   }
 
@@ -121,6 +134,8 @@
     method public androidx.window.embedding.SplitPairRule.Builder setFinishPrimaryWithSecondary(int finishPrimaryWithSecondary);
     method public androidx.window.embedding.SplitPairRule.Builder setFinishSecondaryWithPrimary(int finishSecondaryWithPrimary);
     method public androidx.window.embedding.SplitPairRule.Builder setLayoutDirection(int layoutDirection);
+    method public androidx.window.embedding.SplitPairRule.Builder setMaxAspectRatioInLandscape(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
+    method public androidx.window.embedding.SplitPairRule.Builder setMaxAspectRatioInPortrait(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
     method public androidx.window.embedding.SplitPairRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
     method public androidx.window.embedding.SplitPairRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
     method public androidx.window.embedding.SplitPairRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
@@ -142,6 +157,8 @@
     method public androidx.window.embedding.SplitPlaceholderRule build();
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setFinishPrimaryWithPlaceholder(int finishPrimaryWithPlaceholder);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setLayoutDirection(int layoutDirection);
+    method public androidx.window.embedding.SplitPlaceholderRule.Builder setMaxAspectRatioInLandscape(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
+    method public androidx.window.embedding.SplitPlaceholderRule.Builder setMaxAspectRatioInPortrait(androidx.window.embedding.EmbeddingAspectRatio aspectRatio);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinSmallestWidthDp(@IntRange(from=0L) int minSmallestWidthDp);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setMinWidthDp(@IntRange(from=0L) int minWidthDp);
     method public androidx.window.embedding.SplitPlaceholderRule.Builder setSplitRatio(@FloatRange(from=0.0, to=1.0) float splitRatio);
@@ -150,18 +167,25 @@
 
   public class SplitRule extends androidx.window.embedding.EmbeddingRule {
     method public final int getLayoutDirection();
+    method public final androidx.window.embedding.EmbeddingAspectRatio getMaxAspectRatioInLandscape();
+    method public final androidx.window.embedding.EmbeddingAspectRatio getMaxAspectRatioInPortrait();
     method public final int getMinSmallestWidthDp();
     method public final int getMinWidthDp();
     method public final float getSplitRatio();
     property public final int layoutDirection;
+    property public final androidx.window.embedding.EmbeddingAspectRatio maxAspectRatioInLandscape;
+    property public final androidx.window.embedding.EmbeddingAspectRatio maxAspectRatioInPortrait;
     property public final int minSmallestWidthDp;
     property public final int minWidthDp;
     property public final float splitRatio;
     field public static final androidx.window.embedding.SplitRule.Companion Companion;
-    field public static final int DEFAULT_SPLIT_MIN_DIMENSION_DP = 600; // 0x258
     field public static final int FINISH_ADJACENT = 2; // 0x2
     field public static final int FINISH_ALWAYS = 1; // 0x1
     field public static final int FINISH_NEVER = 0; // 0x0
+    field public static final androidx.window.embedding.EmbeddingAspectRatio SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT;
+    field public static final androidx.window.embedding.EmbeddingAspectRatio SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT;
+    field public static final int SPLIT_MIN_DIMENSION_ALWAYS_ALLOW = 0; // 0x0
+    field public static final int SPLIT_MIN_DIMENSION_DP_DEFAULT = 600; // 0x258
   }
 
   public static final class SplitRule.Companion {
diff --git a/window/window/src/androidTest/java/androidx/window/embedding/EmbeddingRuleConstructionTests.kt b/window/window/src/androidTest/java/androidx/window/embedding/EmbeddingRuleConstructionTests.kt
index cb44e6b..8203078 100644
--- a/window/window/src/androidTest/java/androidx/window/embedding/EmbeddingRuleConstructionTests.kt
+++ b/window/window/src/androidTest/java/androidx/window/embedding/EmbeddingRuleConstructionTests.kt
@@ -22,10 +22,16 @@
 import android.graphics.Rect
 import android.util.LayoutDirection
 import androidx.test.core.app.ApplicationProvider
-import androidx.window.embedding.SplitRule.Companion.DEFAULT_SPLIT_MIN_DIMENSION_DP
+import androidx.window.embedding.EmbeddingAspectRatio.Companion.alwaysAllow
+import androidx.window.embedding.EmbeddingAspectRatio.Companion.alwaysDisallow
+import androidx.window.embedding.EmbeddingAspectRatio.Companion.ratio
+import androidx.window.embedding.SplitRule.Companion.SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT
+import androidx.window.embedding.SplitRule.Companion.SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
+import androidx.window.embedding.SplitRule.Companion.SPLIT_MIN_DIMENSION_DP_DEFAULT
 import androidx.window.embedding.SplitRule.Companion.FINISH_ADJACENT
 import androidx.window.embedding.SplitRule.Companion.FINISH_ALWAYS
 import androidx.window.embedding.SplitRule.Companion.FINISH_NEVER
+import androidx.window.embedding.SplitRule.Companion.SPLIT_MIN_DIMENSION_ALWAYS_ALLOW
 import androidx.window.test.R
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
@@ -52,6 +58,10 @@
             .parseRules(application, R.xml.test_split_config_default_split_pair_rule)
         assertEquals(1, rules.size)
         val rule: SplitPairRule = rules.first() as SplitPairRule
+        assertEquals(SPLIT_MIN_DIMENSION_DP_DEFAULT, rule.minWidthDp)
+        assertEquals(SPLIT_MIN_DIMENSION_DP_DEFAULT, rule.minSmallestWidthDp)
+        assertEquals(SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT, rule.maxAspectRatioInPortrait)
+        assertEquals(SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT, rule.maxAspectRatioInLandscape)
         assertEquals(FINISH_NEVER, rule.finishPrimaryWithSecondary)
         assertEquals(FINISH_ALWAYS, rule.finishSecondaryWithPrimary)
         assertEquals(false, rule.clearTop)
@@ -62,12 +72,37 @@
     }
 
     /**
+     * Verifies that params are set correctly when reading {@link SplitPairRule} from XML.
+     * @see R.xml.test_split_config_custom_split_pair_rule for customized value.
+     */
+    @Test
+    fun testCustom_SplitPairRule_Xml() {
+        val rules = RuleController
+            .parseRules(application, R.xml.test_split_config_custom_split_pair_rule)
+        assertEquals(1, rules.size)
+        val rule: SplitPairRule = rules.first() as SplitPairRule
+        assertEquals(123, rule.minWidthDp)
+        assertEquals(456, rule.minSmallestWidthDp)
+        assertEquals(1.23f, rule.maxAspectRatioInPortrait.value)
+        assertEquals(alwaysDisallow(), rule.maxAspectRatioInLandscape)
+        assertEquals(FINISH_ALWAYS, rule.finishPrimaryWithSecondary)
+        assertEquals(FINISH_NEVER, rule.finishSecondaryWithPrimary)
+        assertEquals(true, rule.clearTop)
+        assertEquals(0.1f, rule.splitRatio)
+        assertEquals(LayoutDirection.RTL, rule.layoutDirection)
+    }
+
+    /**
      * Verifies that default params are set correctly when creating {@link SplitPairRule} with a
      * builder.
      */
     @Test
     fun testDefaults_SplitPairRule_Builder() {
         val rule = SplitPairRule.Builder(HashSet()).build()
+        assertEquals(SPLIT_MIN_DIMENSION_DP_DEFAULT, rule.minWidthDp)
+        assertEquals(SPLIT_MIN_DIMENSION_DP_DEFAULT, rule.minSmallestWidthDp)
+        assertEquals(SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT, rule.maxAspectRatioInPortrait)
+        assertEquals(SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT, rule.maxAspectRatioInLandscape)
         assertEquals(FINISH_NEVER, rule.finishPrimaryWithSecondary)
         assertEquals(FINISH_ALWAYS, rule.finishSecondaryWithPrimary)
         assertEquals(false, rule.clearTop)
@@ -94,6 +129,8 @@
         val rule = SplitPairRule.Builder(filters)
             .setMinWidthDp(123)
             .setMinSmallestWidthDp(456)
+            .setMaxAspectRatioInPortrait(ratio(1.23f))
+            .setMaxAspectRatioInLandscape(ratio(4.56f))
             .setFinishPrimaryWithSecondary(FINISH_ADJACENT)
             .setFinishSecondaryWithPrimary(FINISH_ADJACENT)
             .setClearTop(true)
@@ -108,6 +145,8 @@
         assertEquals(filters, rule.filters)
         assertEquals(123, rule.minWidthDp)
         assertEquals(456, rule.minSmallestWidthDp)
+        assertEquals(1.23f, rule.maxAspectRatioInPortrait.value)
+        assertEquals(4.56f, rule.maxAspectRatioInLandscape.value)
     }
 
     /**
@@ -142,6 +181,126 @@
                 .setSplitRatio(1.1f)
                 .build()
         }
+        assertThrows(IllegalArgumentException::class.java) {
+            SplitPairRule.Builder(HashSet())
+                .setMaxAspectRatioInPortrait(ratio(-1f))
+                .build()
+        }
+        assertThrows(IllegalArgumentException::class.java) {
+            SplitPairRule.Builder(HashSet())
+                .setMaxAspectRatioInLandscape(ratio(-1f))
+                .build()
+        }
+    }
+
+    /**
+     * Verifies that the SplitPairRule verifies that the parent bounds satisfy
+     * maxAspectRatioInPortrait.
+     */
+    @Test
+    fun testSplitPairRule_maxAspectRatioInPortrait() {
+        // Always allow split
+        var rule = SplitPairRule.Builder(HashSet())
+            .setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMaxAspectRatioInLandscape(alwaysAllow())
+            .setMaxAspectRatioInPortrait(alwaysAllow())
+            .build()
+        var width = 100
+        var height = 1000
+        var bounds = Rect(0, 0, width, height)
+        assertTrue(rule.checkParentBounds(density, bounds))
+
+        // Always disallow split in portrait
+        rule = SplitPairRule.Builder(HashSet())
+            .setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMaxAspectRatioInLandscape(alwaysAllow())
+            .setMaxAspectRatioInPortrait(alwaysDisallow())
+            .build()
+        width = 100
+        height = 101
+        bounds = Rect(0, 0, width, height)
+        assertFalse(rule.checkParentBounds(density, bounds))
+        // Ignore if the bounds in landscape
+        bounds = Rect(0, 0, height, width)
+        assertTrue(rule.checkParentBounds(density, bounds))
+
+        // Compare the aspect ratio in portrait
+        rule = SplitPairRule.Builder(HashSet())
+            .setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMaxAspectRatioInLandscape(alwaysAllow())
+            .setMaxAspectRatioInPortrait(ratio(1.1f))
+            .build()
+        // Equals to the max aspect ratio
+        width = 100
+        height = 110
+        bounds = Rect(0, 0, width, height)
+        assertTrue(rule.checkParentBounds(density, bounds))
+        // Greater than the max aspect ratio
+        width = 100
+        height = 111
+        bounds = Rect(0, 0, width, height)
+        assertFalse(rule.checkParentBounds(density, bounds))
+        // Ignore if the bounds in landscape
+        bounds = Rect(0, 0, height, width)
+        assertTrue(rule.checkParentBounds(density, bounds))
+    }
+
+    /**
+     * Verifies that the SplitPairRule verifies that the parent bounds satisfy
+     * maxAspectRatioInLandscape.
+     */
+    @Test
+    fun testSplitPairRule_maxAspectRatioInLandscape() {
+        // Always allow split
+        var rule = SplitPairRule.Builder(HashSet())
+            .setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMaxAspectRatioInPortrait(alwaysAllow())
+            .setMaxAspectRatioInLandscape(alwaysAllow())
+            .build()
+        var width = 1000
+        var height = 100
+        var bounds = Rect(0, 0, width, height)
+        assertTrue(rule.checkParentBounds(density, bounds))
+
+        // Always disallow split in landscape
+        rule = SplitPairRule.Builder(HashSet())
+            .setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMaxAspectRatioInPortrait(alwaysAllow())
+            .setMaxAspectRatioInLandscape(alwaysDisallow())
+            .build()
+        width = 101
+        height = 100
+        bounds = Rect(0, 0, width, height)
+        assertFalse(rule.checkParentBounds(density, bounds))
+        // Ignore if the bounds in portrait
+        bounds = Rect(0, 0, height, width)
+        assertTrue(rule.checkParentBounds(density, bounds))
+
+        // Compare the aspect ratio in landscape
+        rule = SplitPairRule.Builder(HashSet())
+            .setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMaxAspectRatioInPortrait(alwaysAllow())
+            .setMaxAspectRatioInLandscape(ratio(1.1f))
+            .build()
+        // Equals to the max aspect ratio
+        width = 110
+        height = 100
+        bounds = Rect(0, 0, width, height)
+        assertTrue(rule.checkParentBounds(density, bounds))
+        // Greater than the max aspect ratio
+        width = 111
+        height = 100
+        bounds = Rect(0, 0, width, height)
+        assertFalse(rule.checkParentBounds(density, bounds))
+        // Ignore if the bounds in portrait
+        bounds = Rect(0, 0, height, width)
+        assertTrue(rule.checkParentBounds(density, bounds))
     }
 
     /**
@@ -154,6 +313,10 @@
             .parseRules(application, R.xml.test_split_config_default_split_placeholder_rule)
         assertEquals(1, rules.size)
         val rule: SplitPlaceholderRule = rules.first() as SplitPlaceholderRule
+        assertEquals(SPLIT_MIN_DIMENSION_DP_DEFAULT, rule.minWidthDp)
+        assertEquals(SPLIT_MIN_DIMENSION_DP_DEFAULT, rule.minSmallestWidthDp)
+        assertEquals(SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT, rule.maxAspectRatioInPortrait)
+        assertEquals(SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT, rule.maxAspectRatioInLandscape)
         assertEquals(FINISH_ALWAYS, rule.finishPrimaryWithPlaceholder)
         assertEquals(false, rule.isSticky)
         assertEquals(0.5f, rule.splitRatio)
@@ -163,19 +326,42 @@
     }
 
     /**
+     * Verifies that params are set correctly when reading {@link SplitPlaceholderRule} from XML.
+     * @see R.xml.test_split_config_custom_split_placeholder_rule for customized value.
+     */
+    @Test
+    fun testCustom_SplitPlaceholderRule_Xml() {
+        val rules = RuleController
+            .parseRules(application, R.xml.test_split_config_custom_split_placeholder_rule)
+        assertEquals(1, rules.size)
+        val rule: SplitPlaceholderRule = rules.first() as SplitPlaceholderRule
+        assertEquals(123, rule.minWidthDp)
+        assertEquals(456, rule.minSmallestWidthDp)
+        assertEquals(1.23f, rule.maxAspectRatioInPortrait.value)
+        assertEquals(alwaysDisallow(), rule.maxAspectRatioInLandscape)
+        assertEquals(FINISH_ADJACENT, rule.finishPrimaryWithPlaceholder)
+        assertEquals(true, rule.isSticky)
+        assertEquals(0.1f, rule.splitRatio)
+        assertEquals(LayoutDirection.RTL, rule.layoutDirection)
+    }
+
+    /**
      * Verifies that default params are set correctly when creating {@link SplitPlaceholderRule}
      * with a builder.
      */
     @Test
     fun testDefaults_SplitPlaceholderRule_Builder() {
-        val rule = SplitPlaceholderRule.Builder(HashSet(), Intent())
-            .setMinWidthDp(123)
-            .setMinSmallestWidthDp(456)
-            .build()
+        val rule = SplitPlaceholderRule.Builder(HashSet(), Intent()).build()
+        assertEquals(SPLIT_MIN_DIMENSION_DP_DEFAULT, rule.minWidthDp)
+        assertEquals(SPLIT_MIN_DIMENSION_DP_DEFAULT, rule.minSmallestWidthDp)
+        assertEquals(SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT, rule.maxAspectRatioInPortrait)
+        assertEquals(SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT, rule.maxAspectRatioInLandscape)
         assertEquals(FINISH_ALWAYS, rule.finishPrimaryWithPlaceholder)
         assertEquals(false, rule.isSticky)
         assertEquals(0.5f, rule.splitRatio)
         assertEquals(LayoutDirection.LOCALE, rule.layoutDirection)
+        assertTrue(rule.checkParentBounds(density, minValidWindowBounds()))
+        assertFalse(rule.checkParentBounds(density, almostValidWindowBounds()))
     }
 
     /**
@@ -195,6 +381,8 @@
         val rule = SplitPlaceholderRule.Builder(filters, intent)
             .setMinWidthDp(123)
             .setMinSmallestWidthDp(456)
+            .setMaxAspectRatioInPortrait(ratio(1.23f))
+            .setMaxAspectRatioInLandscape(ratio(4.56f))
             .setFinishPrimaryWithPlaceholder(FINISH_ADJACENT)
             .setSticky(true)
             .setSplitRatio(0.3f)
@@ -208,6 +396,8 @@
         assertEquals(intent, rule.placeholderIntent)
         assertEquals(123, rule.minWidthDp)
         assertEquals(456, rule.minSmallestWidthDp)
+        assertEquals(1.23f, rule.maxAspectRatioInPortrait.value)
+        assertEquals(4.56f, rule.maxAspectRatioInLandscape.value)
     }
 
     /**
@@ -249,6 +439,129 @@
                 .setSplitRatio(1.1f)
                 .build()
         }
+        assertThrows(IllegalArgumentException::class.java) {
+            SplitPairRule.Builder(HashSet())
+                .setMaxAspectRatioInPortrait(ratio(-1f))
+                .build()
+        }
+        assertThrows(IllegalArgumentException::class.java) {
+            SplitPairRule.Builder(HashSet())
+                .setMaxAspectRatioInLandscape(ratio(-1f))
+                .build()
+        }
+    }
+
+    /**
+     * Verifies that the SplitPlaceholderRule verifies that the parent bounds satisfy
+     * maxAspectRatioInPortrait.
+     */
+    @Test
+    fun testSplitPlaceholderRule_maxAspectRatioInPortrait() {
+        // Always allow split
+        var rule = SplitPlaceholderRule.Builder(HashSet(), Intent())
+            .setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMaxAspectRatioInLandscape(alwaysAllow())
+            .setMaxAspectRatioInPortrait(alwaysAllow())
+            .build()
+        var width = 100
+        var height = 1000
+        var bounds = Rect(0, 0, width, height)
+        assertTrue(rule.checkParentBounds(density, bounds))
+
+        // Always disallow split in portrait
+        rule = SplitPlaceholderRule.Builder(HashSet(), Intent())
+            .setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMaxAspectRatioInLandscape(alwaysAllow())
+            .setMaxAspectRatioInPortrait(alwaysDisallow())
+            .build()
+        width = 100
+        height = 101
+        bounds = Rect(0, 0, width, height)
+        assertFalse(rule.checkParentBounds(density, bounds))
+        // Ignore if the bounds in landscape
+        bounds = Rect(0, 0, height, width)
+        assertTrue(rule.checkParentBounds(density, bounds))
+
+        // Compare the aspect ratio in portrait
+        rule = SplitPlaceholderRule.Builder(HashSet(), Intent())
+            .setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMaxAspectRatioInLandscape(alwaysAllow())
+            .setMaxAspectRatioInPortrait(ratio(1.1f))
+            .build()
+        // Equals to the max aspect ratio
+        width = 100
+        height = 110
+        bounds = Rect(0, 0, width, height)
+        assertTrue(rule.checkParentBounds(density, bounds))
+        // Greater than the max aspect ratio
+        width = 100
+        height = 111
+        bounds = Rect(0, 0, width, height)
+        assertFalse(rule.checkParentBounds(density, bounds))
+        // Ignore if the bounds in landscape
+        bounds = Rect(0, 0, height, width)
+        assertTrue(rule.checkParentBounds(density, bounds))
+    }
+
+    /**
+     * Verifies that the SplitPlaceholderRule verifies that the parent bounds satisfy
+     * maxAspectRatioInLandscape.
+     */
+    @Test
+    fun testSplitPlaceholderRule_maxAspectRatioInLandscape() {
+        // Always allow split
+        var rule = SplitPlaceholderRule.Builder(HashSet(), Intent())
+            .setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMaxAspectRatioInPortrait(alwaysAllow())
+            .setMaxAspectRatioInLandscape(alwaysAllow())
+            .build()
+        var width = 1000
+        var height = 100
+        var bounds = Rect(0, 0, width, height)
+        assertTrue(rule.checkParentBounds(density, bounds))
+        // Ignore if the bounds in portrait
+        bounds = Rect(0, 0, height, width)
+        assertTrue(rule.checkParentBounds(density, bounds))
+
+        // Always disallow split in landscape
+        rule = SplitPlaceholderRule.Builder(HashSet(), Intent())
+            .setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMaxAspectRatioInPortrait(alwaysAllow())
+            .setMaxAspectRatioInLandscape(alwaysDisallow())
+            .build()
+        width = 101
+        height = 100
+        bounds = Rect(0, 0, width, height)
+        assertFalse(rule.checkParentBounds(density, bounds))
+        // Ignore if the bounds in portrait
+        bounds = Rect(0, 0, height, width)
+        assertTrue(rule.checkParentBounds(density, bounds))
+
+        // Compare the aspect ratio in landscape
+        rule = SplitPlaceholderRule.Builder(HashSet(), Intent())
+            .setMinWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMinSmallestWidthDp(SPLIT_MIN_DIMENSION_ALWAYS_ALLOW)
+            .setMaxAspectRatioInPortrait(alwaysAllow())
+            .setMaxAspectRatioInLandscape(ratio(1.1f))
+            .build()
+        // Equals to the max aspect ratio
+        width = 110
+        height = 100
+        bounds = Rect(0, 0, width, height)
+        assertTrue(rule.checkParentBounds(density, bounds))
+        // Greater than the max aspect ratio
+        width = 111
+        height = 100
+        bounds = Rect(0, 0, width, height)
+        assertFalse(rule.checkParentBounds(density, bounds))
+        // Ignore if the bounds in portrait
+        bounds = Rect(0, 0, height, width)
+        assertTrue(rule.checkParentBounds(density, bounds))
     }
 
     /**
@@ -264,6 +577,19 @@
     }
 
     /**
+     * Verifies that params are set correctly when reading {@link ActivityRule} from XML.
+     * @see R.xml.test_split_config_custom_activity_rule for customized value.
+     */
+    @Test
+    fun testCustom_ActivityRule_Xml() {
+        val rules = RuleController
+            .parseRules(application, R.xml.test_split_config_custom_activity_rule)
+        assertEquals(1, rules.size)
+        val rule: ActivityRule = rules.first() as ActivityRule
+        assertEquals(true, rule.alwaysExpand)
+    }
+
+    /**
      * Verifies that default params are set correctly when creating {@link ActivityRule} with a
      * builder.
      */
@@ -296,7 +622,7 @@
         // Get the screen's density scale
         val scale: Float = density
         // Convert the dps to pixels, based on density scale
-        val minValidWidthPx = (DEFAULT_SPLIT_MIN_DIMENSION_DP * scale + 0.5f).toInt()
+        val minValidWidthPx = (SPLIT_MIN_DIMENSION_DP_DEFAULT * scale + 0.5f).toInt()
 
         return Rect(0, 0, minValidWidthPx, minValidWidthPx)
     }
@@ -305,7 +631,7 @@
         // Get the screen's density scale
         val scale: Float = density
         // Convert the dps to pixels, based on density scale
-        val minValidWidthPx = ((DEFAULT_SPLIT_MIN_DIMENSION_DP) - 1 * scale + 0.5f).toInt()
+        val minValidWidthPx = ((SPLIT_MIN_DIMENSION_DP_DEFAULT) - 1 * scale + 0.5f).toInt()
 
         return Rect(0, 0, minValidWidthPx, minValidWidthPx)
     }
diff --git a/window/window/src/androidTest/res/xml/test_split_config_custom_activity_rule.xml b/window/window/src/androidTest/res/xml/test_split_config_custom_activity_rule.xml
new file mode 100644
index 0000000..63e54b6
--- /dev/null
+++ b/window/window/src/androidTest/res/xml/test_split_config_custom_activity_rule.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+<resources
+    xmlns:window="http://schemas.android.com/apk/res-auto">
+    <ActivityRule
+        window:alwaysExpand="true">
+        <ActivityFilter
+            window:activityName="androidx.window.sample.embedding.SplitActivityList"/>
+    </ActivityRule>
+</resources>
\ No newline at end of file
diff --git a/window/window/src/androidTest/res/xml/test_split_config_custom_split_pair_rule.xml b/window/window/src/androidTest/res/xml/test_split_config_custom_split_pair_rule.xml
new file mode 100644
index 0000000..128eeaa
--- /dev/null
+++ b/window/window/src/androidTest/res/xml/test_split_config_custom_split_pair_rule.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+<resources
+    xmlns:window="http://schemas.android.com/apk/res-auto">
+    <SplitPairRule
+        window:splitRatio="0.1"
+        window:splitMinWidthDp="123"
+        window:splitMinSmallestWidthDp="456"
+        window:splitMaxAspectRatioInPortrait="1.23"
+        window:splitMaxAspectRatioInLandscape="alwaysDisallow"
+        window:splitLayoutDirection="rtl"
+        window:finishPrimaryWithSecondary="always"
+        window:finishSecondaryWithPrimary="never"
+        window:clearTop="true"
+        >
+        <SplitPairFilter
+            window:primaryActivityName="A"
+            window:secondaryActivityName="B"/>
+    </SplitPairRule>
+</resources>
\ No newline at end of file
diff --git a/window/window/src/androidTest/res/xml/test_split_config_custom_split_placeholder_rule.xml b/window/window/src/androidTest/res/xml/test_split_config_custom_split_placeholder_rule.xml
new file mode 100644
index 0000000..3a0716e
--- /dev/null
+++ b/window/window/src/androidTest/res/xml/test_split_config_custom_split_placeholder_rule.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+<resources
+    xmlns:window="http://schemas.android.com/apk/res-auto">
+    <SplitPlaceholderRule
+        window:splitRatio="0.1"
+        window:splitMinWidthDp="123"
+        window:splitMinSmallestWidthDp="456"
+        window:splitMaxAspectRatioInPortrait="1.23"
+        window:splitMaxAspectRatioInLandscape="alwaysDisallow"
+        window:splitLayoutDirection="rtl"
+        window:finishPrimaryWithPlaceholder="adjacent"
+        window:stickyPlaceholder="true"
+        window:placeholderActivityName="C">
+        <ActivityFilter
+            window:activityName="androidx.window.sample.embedding.SplitActivityList"/>
+    </SplitPlaceholderRule>
+</resources>
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/embedding/EmbeddingAspectRatio.kt b/window/window/src/main/java/androidx/window/embedding/EmbeddingAspectRatio.kt
new file mode 100644
index 0000000..643e6fb
--- /dev/null
+++ b/window/window/src/main/java/androidx/window/embedding/EmbeddingAspectRatio.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.window.embedding
+
+import androidx.annotation.FloatRange
+
+/**
+ * The aspect ratio of the parent window bounds to allow embedding with the rule.
+ *
+ * @see SplitRule.maxAspectRatioInPortrait
+ * @see SplitRule.maxAspectRatioInLandscape
+ */
+class EmbeddingAspectRatio private constructor(
+    /**
+     * The description of this `EmbeddingAspectRatio`.
+     */
+    internal val description: String,
+
+    /**
+     * Aspect ratio, expressed as (longer dimension / shorter dimension) in decimal form, of the
+     * parent window bounds.
+     * It will act as special identifiers for [ALWAYS_ALLOW] and [ALWAYS_DISALLOW].
+     */
+    internal val value: Float
+) {
+    override fun toString() = "EmbeddingAspectRatio($description)"
+
+    override fun equals(other: Any?): Boolean {
+        if (other === this) return true
+        if (other !is EmbeddingAspectRatio) return false
+        return value == other.value && description == other.description
+    }
+
+    override fun hashCode() = description.hashCode() + 31 * value.hashCode()
+
+    companion object {
+        /**
+         * For max aspect ratio, when the aspect ratio is greater than this value, it means to
+         * disallow embedding.
+         *
+         * For min aspect ratio, when the aspect ratio is smaller than this value, it means to
+         * disallow embedding.
+         *
+         * Values smaller than or equal to `1` are invalid.
+         *
+         * @param ratio the aspect ratio.
+         * @return the [EmbeddingAspectRatio] representing the [ratio].
+         *
+         * @see alwaysAllow for always allow embedding.
+         * @see alwaysDisallow for always disallow embedding.
+         */
+        @JvmStatic
+        fun ratio(@FloatRange(from = 1.0, fromInclusive = false) ratio: Float):
+            EmbeddingAspectRatio {
+            require(ratio > 1) { "Ratio must be greater than 1." }
+            return EmbeddingAspectRatio("ratio:$ratio", ratio)
+        }
+
+        private val ALWAYS_ALLOW = EmbeddingAspectRatio("ALWAYS_ALLOW", 0f)
+
+        /**
+         * Gets the special [EmbeddingAspectRatio] to represent it always allows embedding.
+         *
+         * An example use case is to set it on [SplitRule.maxAspectRatioInLandscape] if the app
+         * wants to always allow embedding as split when the parent window is in landscape.
+         */
+        @JvmStatic
+        fun alwaysAllow() = ALWAYS_ALLOW
+
+        private val ALWAYS_DISALLOW = EmbeddingAspectRatio("ALWAYS_DISALLOW", -1f)
+
+        /**
+         * Gets the special [EmbeddingAspectRatio] to represent it always disallows embedding.
+         *
+         * An example use case is to set it on [SplitRule.maxAspectRatioInPortrait] if the app
+         * wants to disallow embedding as split when the parent window is in portrait.
+         */
+        @JvmStatic
+        fun alwaysDisallow() = ALWAYS_DISALLOW
+
+        /**
+         * Returns a [EmbeddingAspectRatio] with the given [value].
+         */
+        internal fun buildAspectRatioFromValue(value: Float): EmbeddingAspectRatio {
+            return when (value) {
+                ALWAYS_ALLOW.value -> {
+                    alwaysAllow()
+                }
+                ALWAYS_DISALLOW.value -> {
+                    alwaysDisallow()
+                }
+                else -> {
+                    ratio(value)
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/embedding/RuleParser.kt b/window/window/src/main/java/androidx/window/embedding/RuleParser.kt
index 0c81a18..5d3cea3 100644
--- a/window/window/src/main/java/androidx/window/embedding/RuleParser.kt
+++ b/window/window/src/main/java/androidx/window/embedding/RuleParser.kt
@@ -25,6 +25,7 @@
 import androidx.annotation.XmlRes
 
 import androidx.window.R
+import androidx.window.embedding.EmbeddingAspectRatio.Companion.buildAspectRatioFromValue
 import androidx.window.embedding.SplitRule.Companion.FINISH_ALWAYS
 import androidx.window.embedding.SplitRule.Companion.FINISH_NEVER
 
@@ -126,6 +127,8 @@
         val ratio: Float
         val minWidthDp: Int
         val minSmallestWidthDp: Int
+        val maxAspectRatioInPortrait: Float
+        val maxAspectRatioInLandscape: Float
         val layoutDir: Int
         val finishPrimaryWithSecondary: Int
         val finishSecondaryWithPrimary: Int
@@ -139,11 +142,19 @@
             ratio = getFloat(R.styleable.SplitPairRule_splitRatio, 0.5f)
             minWidthDp = getInteger(
                 R.styleable.SplitPairRule_splitMinWidthDp,
-                SplitRule.DEFAULT_SPLIT_MIN_DIMENSION_DP
+                SplitRule.SPLIT_MIN_DIMENSION_DP_DEFAULT
             )
             minSmallestWidthDp = getInteger(
                 R.styleable.SplitPairRule_splitMinSmallestWidthDp,
-                SplitRule.DEFAULT_SPLIT_MIN_DIMENSION_DP
+                SplitRule.SPLIT_MIN_DIMENSION_DP_DEFAULT
+            )
+            maxAspectRatioInPortrait = getFloat(
+                R.styleable.SplitPairRule_splitMaxAspectRatioInPortrait,
+                SplitRule.SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT.value
+            )
+            maxAspectRatioInLandscape = getFloat(
+                R.styleable.SplitPairRule_splitMaxAspectRatioInLandscape,
+                SplitRule.SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT.value
             )
             layoutDir = getInt(
                 R.styleable.SplitPairRule_splitLayoutDirection,
@@ -159,6 +170,8 @@
         return SplitPairRule.Builder(emptySet())
             .setMinWidthDp(minWidthDp)
             .setMinSmallestWidthDp(minSmallestWidthDp)
+            .setMaxAspectRatioInPortrait(buildAspectRatioFromValue(maxAspectRatioInPortrait))
+            .setMaxAspectRatioInLandscape(buildAspectRatioFromValue(maxAspectRatioInLandscape))
             .setFinishPrimaryWithSecondary(finishPrimaryWithSecondary)
             .setFinishSecondaryWithPrimary(finishSecondaryWithPrimary)
             .setClearTop(clearTop)
@@ -177,6 +190,8 @@
         val ratio: Float
         val minWidthDp: Int
         val minSmallestWidthDp: Int
+        val maxAspectRatioInPortrait: Float
+        val maxAspectRatioInLandscape: Float
         val layoutDir: Int
         context.theme.obtainStyledAttributes(
             parser,
@@ -194,11 +209,19 @@
             ratio = getFloat(R.styleable.SplitPlaceholderRule_splitRatio, 0.5f)
             minWidthDp = getInteger(
                 R.styleable.SplitPlaceholderRule_splitMinWidthDp,
-                SplitRule.DEFAULT_SPLIT_MIN_DIMENSION_DP
+                SplitRule.SPLIT_MIN_DIMENSION_DP_DEFAULT
             )
             minSmallestWidthDp = getInteger(
                 R.styleable.SplitPlaceholderRule_splitMinSmallestWidthDp,
-                SplitRule.DEFAULT_SPLIT_MIN_DIMENSION_DP
+                SplitRule.SPLIT_MIN_DIMENSION_DP_DEFAULT
+            )
+            maxAspectRatioInPortrait = getFloat(
+                R.styleable.SplitPlaceholderRule_splitMaxAspectRatioInPortrait,
+                SplitRule.SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT.value
+            )
+            maxAspectRatioInLandscape = getFloat(
+                R.styleable.SplitPlaceholderRule_splitMaxAspectRatioInLandscape,
+                SplitRule.SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT.value
             )
             layoutDir = getInt(
                 R.styleable.SplitPlaceholderRule_splitLayoutDirection,
@@ -223,6 +246,8 @@
         )
             .setMinWidthDp(minWidthDp)
             .setMinSmallestWidthDp(minSmallestWidthDp)
+            .setMaxAspectRatioInPortrait(buildAspectRatioFromValue(maxAspectRatioInPortrait))
+            .setMaxAspectRatioInLandscape(buildAspectRatioFromValue(maxAspectRatioInLandscape))
             .setSticky(stickyPlaceholder)
             .setFinishPrimaryWithPlaceholder(finishPrimaryWithPlaceholder)
             .setSplitRatio(ratio)
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitPairRule.kt b/window/window/src/main/java/androidx/window/embedding/SplitPairRule.kt
index 1969f04..0b684a4 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitPairRule.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitPairRule.kt
@@ -19,8 +19,6 @@
 import android.util.LayoutDirection.LOCALE
 import androidx.annotation.FloatRange
 import androidx.annotation.IntRange
-import androidx.core.util.Preconditions.checkArgument
-import androidx.core.util.Preconditions.checkArgumentNonnegative
 
 /**
  * Split configuration rules for activity pairs. Define when activities that were launched on top of
@@ -64,14 +62,14 @@
         @SplitFinishBehavior finishPrimaryWithSecondary: Int = FINISH_NEVER,
         @SplitFinishBehavior finishSecondaryWithPrimary: Int = FINISH_ALWAYS,
         clearTop: Boolean = false,
-        @IntRange(from = 0) minWidthDp: Int = DEFAULT_SPLIT_MIN_DIMENSION_DP,
-        @IntRange(from = 0) minSmallestWidthDp: Int = DEFAULT_SPLIT_MIN_DIMENSION_DP,
-        @FloatRange(from = 0.0, to = 1.0) splitRatio: Float = 0.5f,
+        @IntRange(from = 0) minWidthDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
+        @IntRange(from = 0) minSmallestWidthDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
+        maxAspectRatioInPortrait: EmbeddingAspectRatio = SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT,
+        maxAspectRatioInLandscape: EmbeddingAspectRatio = SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT,
+        @FloatRange(from = 0.0, to = 1.0) splitRatio: Float = SPLIT_RATIO_DEFAULT,
         @LayoutDirection layoutDirection: Int = LOCALE
-    ) : super(minWidthDp, minSmallestWidthDp, splitRatio, layoutDirection) {
-        checkArgumentNonnegative(minWidthDp, "minWidthDp must be non-negative")
-        checkArgumentNonnegative(minSmallestWidthDp, "minSmallestWidthDp must be non-negative")
-        checkArgument(splitRatio in 0.0..1.0, "splitRatio must be in 0.0..1.0 range")
+    ) : super(minWidthDp, minSmallestWidthDp, maxAspectRatioInPortrait, maxAspectRatioInLandscape,
+        splitRatio, layoutDirection) {
         this.filters = filters.toSet()
         this.clearTop = clearTop
         this.finishPrimaryWithSecondary = finishPrimaryWithSecondary
@@ -87,18 +85,20 @@
         private val filters: Set<SplitPairFilter>,
     ) {
         @IntRange(from = 0)
-        private var minWidthDp: Int = DEFAULT_SPLIT_MIN_DIMENSION_DP
+        private var minWidthDp = SPLIT_MIN_DIMENSION_DP_DEFAULT
         @IntRange(from = 0)
-        private var minSmallestWidthDp: Int = DEFAULT_SPLIT_MIN_DIMENSION_DP
+        private var minSmallestWidthDp = SPLIT_MIN_DIMENSION_DP_DEFAULT
+        private var maxAspectRatioInPortrait = SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
+        private var maxAspectRatioInLandscape = SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT
         @SplitFinishBehavior
-        private var finishPrimaryWithSecondary: Int = FINISH_NEVER
+        private var finishPrimaryWithSecondary = FINISH_NEVER
         @SplitFinishBehavior
-        private var finishSecondaryWithPrimary: Int = FINISH_ALWAYS
-        private var clearTop: Boolean = false
+        private var finishSecondaryWithPrimary = FINISH_ALWAYS
+        private var clearTop = false
         @FloatRange(from = 0.0, to = 1.0)
-        private var splitRatio: Float = 0.5f
+        private var splitRatio = SPLIT_RATIO_DEFAULT
         @LayoutDirection
-        private var layoutDirection: Int = LOCALE
+        private var layoutDirection = LOCALE
 
         /**
          * @see SplitPairRule.minWidthDp
@@ -113,6 +113,18 @@
             apply { this.minSmallestWidthDp = minSmallestWidthDp }
 
         /**
+         * @see SplitPairRule.maxAspectRatioInPortrait
+         */
+        fun setMaxAspectRatioInPortrait(aspectRatio: EmbeddingAspectRatio): Builder =
+            apply { this.maxAspectRatioInPortrait = aspectRatio }
+
+        /**
+         * @see SplitPairRule.maxAspectRatioInLandscape
+         */
+        fun setMaxAspectRatioInLandscape(aspectRatio: EmbeddingAspectRatio): Builder =
+            apply { this.maxAspectRatioInLandscape = aspectRatio }
+
+        /**
          * @see SplitPairRule.finishPrimaryWithSecondary
          */
         fun setFinishPrimaryWithSecondary(
@@ -148,7 +160,8 @@
             apply { this.layoutDirection = layoutDirection }
 
         fun build() = SplitPairRule(filters, finishPrimaryWithSecondary, finishSecondaryWithPrimary,
-            clearTop, minWidthDp, minSmallestWidthDp, splitRatio, layoutDirection)
+            clearTop, minWidthDp, minSmallestWidthDp, maxAspectRatioInPortrait,
+            maxAspectRatioInLandscape, splitRatio, layoutDirection)
     }
 
     /**
@@ -162,6 +175,8 @@
         return Builder(newSet.toSet())
             .setMinWidthDp(minWidthDp)
             .setMinSmallestWidthDp(minSmallestWidthDp)
+            .setMaxAspectRatioInPortrait(maxAspectRatioInPortrait)
+            .setMaxAspectRatioInLandscape(maxAspectRatioInLandscape)
             .setFinishPrimaryWithSecondary(finishPrimaryWithSecondary)
             .setFinishSecondaryWithPrimary(finishSecondaryWithPrimary)
             .setClearTop(clearTop)
@@ -191,4 +206,18 @@
         result = 31 * result + clearTop.hashCode()
         return result
     }
+
+    override fun toString(): String =
+        "${SplitPairRule::class.java.simpleName}{" +
+            " splitRatio=$splitRatio" +
+            ", layoutDirection=$layoutDirection" +
+            ", minWidthDp=$minWidthDp" +
+            ", minSmallestWidthDp=$minSmallestWidthDp" +
+            ", maxAspectRatioInPortrait=$maxAspectRatioInPortrait" +
+            ", maxAspectRatioInLandscape=$maxAspectRatioInLandscape" +
+            ", clearTop=$clearTop" +
+            ", finishPrimaryWithSecondary=$finishPrimaryWithSecondary" +
+            ", finishSecondaryWithPrimary=$finishSecondaryWithPrimary" +
+            ", filters=$filters" +
+            "}"
 }
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitPlaceholderRule.kt b/window/window/src/main/java/androidx/window/embedding/SplitPlaceholderRule.kt
index 6250e82..fe5f896 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitPlaceholderRule.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitPlaceholderRule.kt
@@ -22,7 +22,6 @@
 import androidx.annotation.IntDef
 import androidx.annotation.IntRange
 import androidx.core.util.Preconditions.checkArgument
-import androidx.core.util.Preconditions.checkArgumentNonnegative
 
 /**
  * Configuration rules for split placeholders.
@@ -81,14 +80,14 @@
         placeholderIntent: Intent,
         isSticky: Boolean,
         @SplitPlaceholderFinishBehavior finishPrimaryWithPlaceholder: Int = FINISH_ALWAYS,
-        @IntRange(from = 0) minWidthDp: Int = DEFAULT_SPLIT_MIN_DIMENSION_DP,
-        @IntRange(from = 0) minSmallestWidthDp: Int = DEFAULT_SPLIT_MIN_DIMENSION_DP,
-        @FloatRange(from = 0.0, to = 1.0) splitRatio: Float = 0.5f,
+        @IntRange(from = 0) minWidthDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
+        @IntRange(from = 0) minSmallestWidthDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
+        maxAspectRatioInPortrait: EmbeddingAspectRatio = SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT,
+        maxAspectRatioInLandscape: EmbeddingAspectRatio = SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT,
+        @FloatRange(from = 0.0, to = 1.0) splitRatio: Float = SPLIT_RATIO_DEFAULT,
         @LayoutDirection layoutDirection: Int = LOCALE
-    ) : super(minWidthDp, minSmallestWidthDp, splitRatio, layoutDirection) {
-        checkArgumentNonnegative(minWidthDp, "minWidthDp must be non-negative")
-        checkArgumentNonnegative(minSmallestWidthDp, "minSmallestWidthDp must be non-negative")
-        checkArgument(splitRatio in 0.0..1.0, "splitRatio must be in 0.0..1.0 range")
+    ) : super(minWidthDp, minSmallestWidthDp, maxAspectRatioInPortrait, maxAspectRatioInLandscape,
+        splitRatio, layoutDirection) {
         checkArgument(finishPrimaryWithPlaceholder != FINISH_NEVER,
             "FINISH_NEVER is not a valid configuration for SplitPlaceholderRule. " +
                 "Please use FINISH_ALWAYS or FINISH_ADJACENT instead or refer to the current API.")
@@ -109,16 +108,18 @@
         private val placeholderIntent: Intent,
     ) {
         @IntRange(from = 0)
-        private var minWidthDp: Int = DEFAULT_SPLIT_MIN_DIMENSION_DP
+        private var minWidthDp = SPLIT_MIN_DIMENSION_DP_DEFAULT
         @IntRange(from = 0)
-        private var minSmallestWidthDp: Int = DEFAULT_SPLIT_MIN_DIMENSION_DP
+        private var minSmallestWidthDp = SPLIT_MIN_DIMENSION_DP_DEFAULT
+        private var maxAspectRatioInPortrait = SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
+        private var maxAspectRatioInLandscape = SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT
         @SplitPlaceholderFinishBehavior
-        private var finishPrimaryWithPlaceholder: Int = FINISH_ALWAYS
-        private var isSticky: Boolean = false
+        private var finishPrimaryWithPlaceholder = FINISH_ALWAYS
+        private var isSticky = false
         @FloatRange(from = 0.0, to = 1.0)
-        private var splitRatio: Float = 0.5f
+        private var splitRatio = SPLIT_RATIO_DEFAULT
         @LayoutDirection
-        private var layoutDirection: Int = LOCALE
+        private var layoutDirection = LOCALE
 
         /**
          * @see SplitPlaceholderRule.minWidthDp
@@ -133,6 +134,18 @@
             apply { this.minSmallestWidthDp = minSmallestWidthDp }
 
         /**
+         * @see SplitPlaceholderRule.maxAspectRatioInPortrait
+         */
+        fun setMaxAspectRatioInPortrait(aspectRatio: EmbeddingAspectRatio): Builder =
+            apply { this.maxAspectRatioInPortrait = aspectRatio }
+
+        /**
+         * @see SplitPlaceholderRule.maxAspectRatioInLandscape
+         */
+        fun setMaxAspectRatioInLandscape(aspectRatio: EmbeddingAspectRatio): Builder =
+            apply { this.maxAspectRatioInLandscape = aspectRatio }
+
+        /**
          * @see SplitPlaceholderRule.finishPrimaryWithPlaceholder
          */
         fun setFinishPrimaryWithPlaceholder(
@@ -161,8 +174,8 @@
             apply { this.layoutDirection = layoutDirection }
 
         fun build() = SplitPlaceholderRule(filters, placeholderIntent, isSticky,
-            finishPrimaryWithPlaceholder, minWidthDp, minSmallestWidthDp, splitRatio,
-            layoutDirection)
+            finishPrimaryWithPlaceholder, minWidthDp, minSmallestWidthDp, maxAspectRatioInPortrait,
+            maxAspectRatioInLandscape, splitRatio, layoutDirection)
     }
 
     /**
@@ -176,6 +189,8 @@
         return Builder(newSet.toSet(), placeholderIntent)
             .setMinWidthDp(minWidthDp)
             .setMinSmallestWidthDp(minSmallestWidthDp)
+            .setMaxAspectRatioInPortrait(maxAspectRatioInPortrait)
+            .setMaxAspectRatioInLandscape(maxAspectRatioInLandscape)
             .setSticky(isSticky)
             .setFinishPrimaryWithPlaceholder(finishPrimaryWithPlaceholder)
             .setSplitRatio(splitRatio)
@@ -204,4 +219,18 @@
         result = 31 * result + filters.hashCode()
         return result
     }
+
+    override fun toString(): String =
+        "SplitPlaceholderRule{" +
+            " splitRatio=$splitRatio" +
+            ", layoutDirection=$layoutDirection" +
+            ", minWidthDp=$minWidthDp" +
+            ", minSmallestWidthDp=$minSmallestWidthDp" +
+            ", maxAspectRatioInPortrait=$maxAspectRatioInPortrait" +
+            ", maxAspectRatioInLandscape=$maxAspectRatioInLandscape" +
+            ", placeholderIntent=$placeholderIntent" +
+            ", isSticky=$isSticky" +
+            ", finishPrimaryWithPlaceholder=$finishPrimaryWithPlaceholder" +
+            ", filters=$filters" +
+            "}"
 }
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitRule.kt b/window/window/src/main/java/androidx/window/embedding/SplitRule.kt
index eb773d3..c4f7905 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitRule.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitRule.kt
@@ -28,7 +28,10 @@
 import androidx.annotation.IntDef
 import androidx.annotation.IntRange
 import androidx.annotation.RequiresApi
-import androidx.window.embedding.SplitRule.Companion.DEFAULT_SPLIT_MIN_DIMENSION_DP
+import androidx.core.util.Preconditions
+import androidx.window.embedding.EmbeddingAspectRatio.Companion.alwaysAllow
+import androidx.window.embedding.EmbeddingAspectRatio.Companion.ratio
+import androidx.window.embedding.SplitRule.Companion.SPLIT_MIN_DIMENSION_DP_DEFAULT
 import kotlin.math.min
 
 /**
@@ -46,11 +49,11 @@
      * When the window size is smaller than requested here, activities in the secondary container
      * will be stacked on top of the activities in the primary one, completely overlapping them.
      *
-     * The default is [DEFAULT_SPLIT_MIN_DIMENSION_DP] if the app doesn't set.
-     * `0` means to always allow split.
+     * The default is [SPLIT_MIN_DIMENSION_DP_DEFAULT] if the app doesn't set.
+     * [SPLIT_MIN_DIMENSION_ALWAYS_ALLOW] means to always allow split.
      */
     @IntRange(from = 0)
-    val minWidthDp: Int = DEFAULT_SPLIT_MIN_DIMENSION_DP,
+    val minWidthDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
 
     /**
      * The smallest value of the smallest possible width of the parent window in any rotation
@@ -58,18 +61,53 @@
      * here, activities in the secondary container will be stacked on top of the activities in
      * the primary one, completely overlapping them.
      *
-     * The default is [DEFAULT_SPLIT_MIN_DIMENSION_DP] if the app doesn't set.
-     * `0` means to always allow split.
+     * The default is [SPLIT_MIN_DIMENSION_DP_DEFAULT] if the app doesn't set.
+     * [SPLIT_MIN_DIMENSION_ALWAYS_ALLOW] means to always allow split.
      */
     @IntRange(from = 0)
-    val minSmallestWidthDp: Int,
+    val minSmallestWidthDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
 
     /**
-     * Defines what part of the width should be given to the primary activity. Defaults to an
-     * equal width split.
+     * The largest value of the aspect ratio, expressed as (height / width) in decimal form, of the
+     * parent window bounds in portrait when the split should be used. When the window aspect ratio
+     * is greater than requested here, activities in the secondary container will stacked on top of
+     * the activities in the primary one, completely overlapping them.
+     *
+     * This value is only used when the parent window is in portrait (height >= width).
+     *
+     * The default is [SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT] if the app doesn't set, which is the
+     * recommend value to only allow split when the parent window is not too stretched in portrait.
+     *
+     * @see EmbeddingAspectRatio.ratio
+     * @see EmbeddingAspectRatio.alwaysAllow
+     * @see EmbeddingAspectRatio.alwaysDisallow
+     */
+    val maxAspectRatioInPortrait: EmbeddingAspectRatio = SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT,
+
+    /**
+     * The largest value of the aspect ratio, expressed as (width / height) in decimal form, of the
+     * parent window bounds in landscape when the split should be used. When the window aspect ratio
+     * is greater than requested here, activities in the secondary container will stacked on top of
+     * the activities in the primary one, completely overlapping them.
+     *
+     * This value is only used when the parent window is in landscape (width > height).
+     *
+     * The default is [SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT] if the app doesn't set, which is
+     * the recommend value to always allow split when the parent window is in landscape.
+     *
+     * @see EmbeddingAspectRatio.ratio
+     * @see EmbeddingAspectRatio.alwaysAllow
+     * @see EmbeddingAspectRatio.alwaysDisallow
+     */
+    val maxAspectRatioInLandscape: EmbeddingAspectRatio = SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT,
+
+    /**
+     * Defines what part of the width should be given to the primary activity.
+     *
+     * The default is `0.5` if the app doesn't set, which is to split with equal width.
      */
     @FloatRange(from = 0.0, to = 1.0)
-    val splitRatio: Float = 0.5f,
+    val splitRatio: Float = SPLIT_RATIO_DEFAULT,
 
     /**
      * The layout direction for the split. The value must be one of [LTR], [RTL] or [LOCALE].
@@ -84,6 +122,15 @@
     val layoutDirection: Int = LOCALE
 ) : EmbeddingRule() {
 
+    init {
+        Preconditions.checkArgumentNonnegative(minWidthDp, "minWidthDp must be non-negative")
+        Preconditions.checkArgumentNonnegative(
+            minSmallestWidthDp,
+            "minSmallestWidthDp must be non-negative"
+        )
+        Preconditions.checkArgument(splitRatio in 0.0..1.0, "splitRatio must be in 0.0..1.0 range")
+    }
+
     @IntDef(LTR, RTL, LOCALE)
     @Retention(AnnotationRetention.SOURCE)
     internal annotation class LayoutDirection
@@ -119,11 +166,41 @@
          * @see SplitRule.Companion
          */
         const val FINISH_ADJACENT = 2
+
+        /**
+         * The default split ratio if it is not set by apps.
+         * @see SplitRule.splitRatio
+         */
+        internal const val SPLIT_RATIO_DEFAULT = 0.5f
+
+        /**
+         * When the min dimension is set to this value, it means to always allow split.
+         * @see SplitRule.minWidthDp
+         * @see SplitRule.minSmallestWidthDp
+         */
+        const val SPLIT_MIN_DIMENSION_ALWAYS_ALLOW = 0
+
         /**
          * The default min dimension in DP for allowing split if it is not set by apps. The value
          * reflects [androidx.window.core.layout.WindowWidthSizeClass.MEDIUM].
+         * @see SplitRule.minWidthDp
+         * @see SplitRule.minSmallestWidthDp
          */
-        const val DEFAULT_SPLIT_MIN_DIMENSION_DP = 600
+        const val SPLIT_MIN_DIMENSION_DP_DEFAULT = 600
+
+        /**
+         * The default max aspect ratio for allowing split when the parent window is in portrait.
+         * @see SplitRule.maxAspectRatioInPortrait
+         */
+        @JvmField
+        val SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT = ratio(1.4f)
+
+        /**
+         * The default max aspect ratio for allowing split when the parent window is in landscape.
+         * @see SplitRule.maxAspectRatioInLandscape
+         */
+        @JvmField
+        val SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT = alwaysAllow()
     }
 
     /**
@@ -135,7 +212,8 @@
     internal annotation class SplitFinishBehavior
 
     /**
-     * Verifies if the provided parent bounds are large enough to apply the rule.
+     * Verifies if the provided parent bounds satisfy the dimensions and aspect ratio requirements
+     * to apply the rule.
      */
     internal fun checkParentMetrics(context: Context, parentMetrics: WindowMetrics): Boolean {
         if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
@@ -152,14 +230,27 @@
      * @see checkParentMetrics
      */
     internal fun checkParentBounds(density: Float, bounds: Rect): Boolean {
+        val width = bounds.width()
+        val height = bounds.height()
+        if (width == 0 || height == 0) {
+            return false
+        }
         val minWidthPx = convertDpToPx(density, minWidthDp)
         val minSmallestWidthPx = convertDpToPx(density, minSmallestWidthDp)
-        val validMinWidth = (minWidthDp == 0 || bounds.width() >= minWidthPx)
-        val validSmallestMinWidth = (
-            minSmallestWidthDp == 0 ||
-                min(bounds.width(), bounds.height()) >= minSmallestWidthPx
-            )
-        return validMinWidth && validSmallestMinWidth
+        // Always allow split if the min dimensions are 0.
+        val validMinWidth = minWidthDp == SPLIT_MIN_DIMENSION_ALWAYS_ALLOW || width >= minWidthPx
+        val validSmallestMinWidth = minSmallestWidthDp == SPLIT_MIN_DIMENSION_ALWAYS_ALLOW ||
+            min(width, height) >= minSmallestWidthPx
+        val validAspectRatio = if (height >= width) {
+            // Portrait
+            maxAspectRatioInPortrait == alwaysAllow() ||
+                height * 1f / width <= maxAspectRatioInPortrait.value
+        } else {
+            // Landscape
+            maxAspectRatioInLandscape == alwaysAllow() ||
+                width * 1f / height <= maxAspectRatioInLandscape.value
+        }
+        return validMinWidth && validSmallestMinWidth && validAspectRatio
     }
 
     /**
@@ -183,6 +274,8 @@
 
         if (minWidthDp != other.minWidthDp) return false
         if (minSmallestWidthDp != other.minSmallestWidthDp) return false
+        if (maxAspectRatioInPortrait != other.maxAspectRatioInPortrait) return false
+        if (maxAspectRatioInLandscape != other.maxAspectRatioInLandscape) return false
         if (splitRatio != other.splitRatio) return false
         if (layoutDirection != other.layoutDirection) return false
 
@@ -192,8 +285,20 @@
     override fun hashCode(): Int {
         var result = minWidthDp
         result = 31 * result + minSmallestWidthDp
+        result = 31 * result + maxAspectRatioInPortrait.hashCode()
+        result = 31 * result + maxAspectRatioInLandscape.hashCode()
         result = 31 * result + splitRatio.hashCode()
         result = 31 * result + layoutDirection
         return result
     }
+
+    override fun toString(): String =
+        "${SplitRule::class.java.simpleName}{" +
+            " splitRatio=$splitRatio" +
+            ", layoutDirection=$layoutDirection" +
+            ", minWidthDp=$minWidthDp" +
+            ", minSmallestWidthDp=$minSmallestWidthDp" +
+            ", maxAspectRatioInPortrait=$maxAspectRatioInPortrait" +
+            ", maxAspectRatioInLandscape=$maxAspectRatioInLandscape" +
+            "}"
 }
\ No newline at end of file
diff --git a/window/window/src/main/res/values/attrs.xml b/window/window/src/main/res/values/attrs.xml
index 612d1f2..96527f8 100644
--- a/window/window/src/main/res/values/attrs.xml
+++ b/window/window/src/main/res/values/attrs.xml
@@ -21,8 +21,30 @@
     <!-- The smallest value of width of the parent window when the split should be used. -->
     <attr name="splitMinWidthDp" format="integer" />
     <!-- The smallest value of the smallest-width (sw) of the parent window in any rotation when
-     the split should be used. -->
+    the split should be used. -->
     <attr name="splitMinSmallestWidthDp" format="integer" />
+    <!-- The largest value of the aspect ratio, expressed as (height / width) in decimal form, of
+    the parent window bounds in portrait when the split should be used.
+    `0` or `alwaysAllow` means to always allow split in portrait.
+    `-1` or `alwaysDisallow` means to always disallow split in portrait.
+    Any other values less than 1 are invalid. -->
+    <attr name="splitMaxAspectRatioInPortrait"  format="float">
+        <!-- Special value to always allow split in portrait. -->
+        <enum name="alwaysAllow" value="0" />
+        <!-- Special value to always disallow split in portrait. -->
+        <enum name="alwaysDisallow" value="-1" />
+    </attr>
+    <!-- The largest value of the aspect ratio, expressed as (width / height) in decimal form, of
+    the parent window bounds in landscape when the split should be used.
+    `0` or `alwaysAllow` means to always allow split in landscape.
+    `-1` or `alwaysDisallow` means to always disallow split in landscape.
+    Any other values less than 1 are invalid. -->
+    <attr name="splitMaxAspectRatioInLandscape"  format="float">
+        <!-- Special value to always allow split in landscape. -->
+        <enum name="alwaysAllow" value="0" />
+        <!-- Special value to always disallow split in landscape. -->
+        <enum name="alwaysDisallow" value="-1" />
+    </attr>
     <!-- The layout direction for the split. The value must be one of "ltr", "rtl" or "locale". -->
     <attr name="splitLayoutDirection" format="enum">
         <!-- It splits the task bounds vertically, and the direction is deduced from the default
@@ -67,6 +89,8 @@
         <attr name="splitRatio"/>
         <attr name="splitMinWidthDp"/>
         <attr name="splitMinSmallestWidthDp"/>
+        <attr name="splitMaxAspectRatioInPortrait" />
+        <attr name="splitMaxAspectRatioInLandscape" />
         <attr name="splitLayoutDirection"/>
     </declare-styleable>
 
@@ -85,6 +109,8 @@
         <attr name="splitRatio"/>
         <attr name="splitMinWidthDp"/>
         <attr name="splitMinSmallestWidthDp"/>
+        <attr name="splitMaxAspectRatioInPortrait" />
+        <attr name="splitMaxAspectRatioInLandscape" />
         <attr name="splitLayoutDirection"/>
     </declare-styleable>