Merge "Add lazy scrolling jank benchmark." into androidx-main
diff --git a/activity/OWNERS b/activity/OWNERS
index 6f6aad7..04ec2ce 100644
--- a/activity/OWNERS
+++ b/activity/OWNERS
@@ -1,2 +1,5 @@
 [email protected]
 [email protected]
+
+per-file settings.gradle = [email protected], [email protected]
+
diff --git a/activity/activity-compose/build.gradle b/activity/activity-compose/build.gradle
index e954274..5181863 100644
--- a/activity/activity-compose/build.gradle
+++ b/activity/activity-compose/build.gradle
@@ -30,13 +30,13 @@
 }
 
 dependencies {
-    kotlinPlugin projectOrArtifact(":compose:compiler:compiler")
+    kotlinPlugin "androidx.compose.compiler:compiler:1.0.0-beta02"
 
     implementation(KOTLIN_STDLIB)
-    api projectOrArtifact(":compose:runtime:runtime")
-    api projectOrArtifact(":compose:runtime:runtime-saveable")
-    api(projectOrArtifact(":activity:activity-ktx"))
-    api(projectOrArtifact(":compose:ui:ui"))
+    api "androidx.compose.runtime:runtime:1.0.0-beta02"
+    api "androidx.compose.runtime:runtime-saveable:1.0.0-beta02"
+    api projectOrArtifact(":activity:activity-ktx")
+    api "androidx.compose.ui:ui:1.0.0-beta02"
 
     androidTestImplementation projectOrArtifact(":compose:ui:ui-test-junit4")
     androidTestImplementation projectOrArtifact(":compose:material:material")
diff --git a/activity/activity-compose/integration-tests/activity-demos/lint-baseline.xml b/activity/activity-compose/integration-tests/activity-demos/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/activity/activity-compose/integration-tests/activity-demos/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/activity/activity-compose/lint-baseline.xml b/activity/activity-compose/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/activity/activity-compose/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/activity/activity-compose/samples/lint-baseline.xml b/activity/activity-compose/samples/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/activity/activity-compose/samples/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/activity/activity-ktx/build.gradle b/activity/activity-ktx/build.gradle
index d8fd2f8..279d455 100644
--- a/activity/activity-ktx/build.gradle
+++ b/activity/activity-ktx/build.gradle
@@ -30,16 +30,16 @@
     api("androidx.core:core-ktx:1.1.0") {
         because "Mirror activity dependency graph for -ktx artifacts"
     }
-    api("androidx.lifecycle:lifecycle-runtime-ktx:2.3.0") {
+    api(prebuiltOrSnapshot("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1")) {
         because 'Mirror activity dependency graph for -ktx artifacts'
     }
-    api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0")
+    api(prebuiltOrSnapshot("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"))
     api("androidx.savedstate:savedstate-ktx:1.1.0") {
         because 'Mirror activity dependency graph for -ktx artifacts'
     }
     api(KOTLIN_STDLIB)
 
-    androidTestImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.3.0")
+    androidTestImplementation(prebuiltOrSnapshot("androidx.lifecycle:lifecycle-runtime-testing:2.3.1"))
     androidTestImplementation(JUNIT)
     androidTestImplementation(TRUTH)
     androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
diff --git a/activity/activity-ktx/lint-baseline.xml b/activity/activity-ktx/lint-baseline.xml
deleted file mode 100644
index 8f1aa4b..0000000
--- a/activity/activity-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-</issues>
diff --git a/activity/activity-lint/lint-baseline.xml b/activity/activity-lint/lint-baseline.xml
deleted file mode 100644
index 297ae16..0000000
--- a/activity/activity-lint/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" version="4.2.0-beta02">
-
-</issues>
diff --git a/activity/activity-lint/src/main/java/androidx/activity/lint/ActivityResultFragmentVersionDetector.kt b/activity/activity-lint/src/main/java/androidx/activity/lint/ActivityResultFragmentVersionDetector.kt
index c63e5bd..989efba4 100644
--- a/activity/activity-lint/src/main/java/androidx/activity/lint/ActivityResultFragmentVersionDetector.kt
+++ b/activity/activity-lint/src/main/java/androidx/activity/lint/ActivityResultFragmentVersionDetector.kt
@@ -226,7 +226,7 @@
         return when {
             length < other.length -> true
             length > other.length -> false
-            else -> this < other
+            else -> this > other
         }
     }
 }
diff --git a/activity/activity-lint/src/test/java/androidx/activity/lint/ActivityResultFragmentVersionDetectorTest.kt b/activity/activity-lint/src/test/java/androidx/activity/lint/ActivityResultFragmentVersionDetectorTest.kt
index 0f51894..5686ef3 100644
--- a/activity/activity-lint/src/test/java/androidx/activity/lint/ActivityResultFragmentVersionDetectorTest.kt
+++ b/activity/activity-lint/src/test/java/androidx/activity/lint/ActivityResultFragmentVersionDetectorTest.kt
@@ -57,6 +57,31 @@
     }
 
     @Test
+    fun expectPassRegisterForActivityResultStableVersions() {
+        lint().files(
+            kotlin(
+                """
+                package com.example
+
+                import androidx.activity.result.ActivityResultCaller
+                import androidx.activity.result.contract.ActivityResultContract
+
+                val launcher = ActivityResultCaller().registerForActivityResult(ActivityResultContract())
+            """
+            ),
+            gradle(
+                "build.gradle",
+                """
+                dependencies {
+                    api("androidx.fragment:fragment:1.3.1")
+                }
+            """
+            ).indented()
+        )
+            .run().expectClean()
+    }
+
+    @Test
     fun expectPassRegisterForActivityResultProject() {
         lint().files(
             kotlin(
diff --git a/activity/activity/build.gradle b/activity/activity/build.gradle
index a930abc..9be5f88 100644
--- a/activity/activity/build.gradle
+++ b/activity/activity/build.gradle
@@ -23,13 +23,13 @@
     api("androidx.annotation:annotation:1.1.0")
     implementation("androidx.collection:collection:1.0.0")
     api("androidx.core:core:1.1.0")
-    api("androidx.lifecycle:lifecycle-runtime:2.3.0")
-    api("androidx.lifecycle:lifecycle-viewmodel:2.3.0")
+    api(prebuiltOrSnapshot("androidx.lifecycle:lifecycle-runtime:2.3.1"))
+    api(prebuiltOrSnapshot("androidx.lifecycle:lifecycle-viewmodel:2.3.1"))
     api("androidx.savedstate:savedstate:1.1.0")
-    api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.0")
+    api(prebuiltOrSnapshot("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.1"))
     implementation("androidx.tracing:tracing:1.0.0")
 
-    androidTestImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.3.0")
+    androidTestImplementation(prebuiltOrSnapshot("androidx.lifecycle:lifecycle-runtime-testing:2.3.1"))
     androidTestImplementation(KOTLIN_STDLIB)
     androidTestImplementation(LEAKCANARY)
     androidTestImplementation(LEAKCANARY_INSTRUMENTATION)
diff --git a/activity/activity/lint-baseline.xml b/activity/activity/lint-baseline.xml
index 3c4f648..7819beb 100644
--- a/activity/activity/lint-baseline.xml
+++ b/activity/activity/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
+<issues format="5" by="lint 4.2.0-beta04" client="gradle" variant="debug" version="4.2.0-beta04">
 
     <issue
         id="BanTargetApiAnnotation"
@@ -8,7 +8,7 @@
         errorLine2="    ~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/activity/result/contract/ActivityResultContracts.java"
-            line="458"
+            line="459"
             column="5"/>
     </issue>
 
@@ -19,7 +19,7 @@
         errorLine2="    ~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/activity/result/contract/ActivityResultContracts.java"
-            line="521"
+            line="523"
             column="5"/>
     </issue>
 
@@ -30,7 +30,7 @@
         errorLine2="    ~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/activity/result/contract/ActivityResultContracts.java"
-            line="559"
+            line="561"
             column="5"/>
     </issue>
 
@@ -41,7 +41,7 @@
         errorLine2="    ~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/activity/result/contract/ActivityResultContracts.java"
-            line="601"
+            line="603"
             column="5"/>
     </issue>
 
@@ -52,7 +52,7 @@
         errorLine2="    ~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/activity/result/contract/ActivityResultContracts.java"
-            line="640"
+            line="642"
             column="5"/>
     </issue>
 
@@ -63,7 +63,7 @@
         errorLine2="                                       ~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/activity/result/contract/ActivityResultContracts.java"
-            line="495"
+            line="497"
             column="40"/>
     </issue>
 
@@ -74,7 +74,7 @@
         errorLine2="                                  ~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/activity/ComponentActivity.java"
-            line="246"
+            line="232"
             column="35"/>
     </issue>
 
diff --git a/activity/settings.gradle b/activity/settings.gradle
index 9975849..387f67e 100644
--- a/activity/settings.gradle
+++ b/activity/settings.gradle
@@ -21,8 +21,9 @@
 selectProjectsFromAndroidX({ name ->
     if (name.startsWith(":activity")) return true
     if (name == ":annotation:annotation-sampled") return true
-    if (name.startsWith(":internal-testutils-runtime")) return true
-    if (name == ":compose:internal-lint-checks") return true
+    if (name == ":internal-testutils-runtime") return true
+    if (name == ":compose:lint:common") return true
+    if (name == ":compose:lint:internal-lint-checks") return true
     return false
 })
 
diff --git a/annotation/annotation-experimental-lint/integration-tests/lint-baseline.xml b/annotation/annotation-experimental-lint/integration-tests/lint-baseline.xml
index 29c92ce..7f7d50f 100644
--- a/annotation/annotation-experimental-lint/integration-tests/lint-baseline.xml
+++ b/annotation/annotation-experimental-lint/integration-tests/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
+<issues format="5" by="lint 4.2.0-beta04" client="gradle" variant="debug" version="4.2.0-beta04">
 
     <issue
         id="UnsafeOptInUsageError"
diff --git a/annotation/annotation-experimental-lint/lint-baseline.xml b/annotation/annotation-experimental-lint/lint-baseline.xml
deleted file mode 100644
index 297ae16..0000000
--- a/annotation/annotation-experimental-lint/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" version="4.2.0-beta02">
-
-</issues>
diff --git a/annotation/annotation-experimental/lint-baseline.xml b/annotation/annotation-experimental/lint-baseline.xml
deleted file mode 100644
index 8f1aa4b..0000000
--- a/annotation/annotation-experimental/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-</issues>
diff --git a/annotation/annotation-experimental/proguard-rules.pro b/annotation/annotation-experimental/proguard-rules.pro
index 8392750..d1c676bf 100644
--- a/annotation/annotation-experimental/proguard-rules.pro
+++ b/annotation/annotation-experimental/proguard-rules.pro
@@ -14,7 +14,9 @@
 
 # Ignore missing Kotlin meta-annotations so that this library can be used
 # without adding a compileOnly dependency on the Kotlin standard library.
+-dontwarn kotlin.Deprecated
 -dontwarn kotlin.Metadata
+-dontwarn kotlin.ReplaceWith
 -dontwarn kotlin.annotation.AnnotationRetention
 -dontwarn kotlin.annotation.AnnotationTarget
 -dontwarn kotlin.annotation.Retention
diff --git a/annotation/annotation-sampled/lint-baseline.xml b/annotation/annotation-sampled/lint-baseline.xml
deleted file mode 100644
index 297ae16..0000000
--- a/annotation/annotation-sampled/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" version="4.2.0-beta02">
-
-</issues>
diff --git a/annotation/annotation/lint-baseline.xml b/annotation/annotation/lint-baseline.xml
deleted file mode 100644
index 297ae16..0000000
--- a/annotation/annotation/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" version="4.2.0-beta02">
-
-</issues>
diff --git a/appcompat/appcompat-resources/api/restricted_current.txt b/appcompat/appcompat-resources/api/restricted_current.txt
index 84adee2..9ea3d58 100644
--- a/appcompat/appcompat-resources/api/restricted_current.txt
+++ b/appcompat/appcompat-resources/api/restricted_current.txt
@@ -87,10 +87,15 @@
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class VectorEnabledTintResources extends android.content.res.Resources {
     ctor public VectorEnabledTintResources(android.content.Context, android.content.res.Resources);
+    method public int getColor(int) throws android.content.res.Resources.NotFoundException;
+    method public android.content.res.ColorStateList! getColorStateList(int) throws android.content.res.Resources.NotFoundException;
     method public android.graphics.drawable.Drawable! getDrawable(int) throws android.content.res.Resources.NotFoundException;
+    method @RequiresApi(15) public android.graphics.drawable.Drawable! getDrawableForDensity(int, int) throws android.content.res.Resources.NotFoundException;
+    method public android.graphics.Movie! getMovie(int) throws android.content.res.Resources.NotFoundException;
     method public static boolean isCompatVectorFromResourcesEnabled();
     method public static void setCompatVectorFromResourcesEnabled(boolean);
     method public static boolean shouldBeUsed();
+    method public void updateConfiguration(android.content.res.Configuration!, android.util.DisplayMetrics!);
     field public static final int MAX_SDK_WHERE_REQUIRED = 20; // 0x14
   }
 
diff --git a/appcompat/appcompat-resources/src/androidTest/java/androidx/appcompat/widget/TestResources.java b/appcompat/appcompat-resources/src/androidTest/java/androidx/appcompat/widget/TestResources.java
new file mode 100644
index 0000000..a50588f
--- /dev/null
+++ b/appcompat/appcompat-resources/src/androidTest/java/androidx/appcompat/widget/TestResources.java
@@ -0,0 +1,46 @@
+/*
+ * 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.appcompat.widget;
+
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+
+/**
+ * Observable Resources class.
+ */
+@SuppressWarnings("deprecation")
+class TestResources extends Resources {
+    private boolean mGetDrawableCalled;
+
+    TestResources(Resources res) {
+        super(res.getAssets(), res.getDisplayMetrics(), res.getConfiguration());
+    }
+
+    @Override
+    public Drawable getDrawable(int id) throws NotFoundException {
+        mGetDrawableCalled = true;
+        return super.getDrawable(id);
+    }
+
+    public void resetGetDrawableCalled() {
+        mGetDrawableCalled = false;
+    }
+
+    public boolean wasGetDrawableCalled() {
+        return mGetDrawableCalled;
+    }
+}
diff --git a/appcompat/appcompat-resources/src/androidTest/java/androidx/appcompat/widget/TintResourcesTest.java b/appcompat/appcompat-resources/src/androidTest/java/androidx/appcompat/widget/TintResourcesTest.java
index 313399e..4aec28c 100644
--- a/appcompat/appcompat-resources/src/androidTest/java/androidx/appcompat/widget/TintResourcesTest.java
+++ b/appcompat/appcompat-resources/src/androidTest/java/androidx/appcompat/widget/TintResourcesTest.java
@@ -21,7 +21,6 @@
 
 import android.app.Activity;
 import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -38,11 +37,16 @@
     public final androidx.test.rule.ActivityTestRule<Activity> mActivityTestRule =
             new androidx.test.rule.ActivityTestRule<>(Activity.class);
 
+    /**
+     * Ensures that TintResources delegates calls to the wrapped Resources object.
+     */
     @Test
     public void testTintResourcesDelegateBackToOriginalResources() {
         final TestResources testResources =
                 new TestResources(mActivityTestRule.getActivity().getResources());
+
         // First make sure that the flag is false
+        testResources.resetGetDrawableCalled();
         assertFalse(testResources.wasGetDrawableCalled());
 
         // Now wrap in a TintResources instance and get a Drawable
@@ -53,26 +57,4 @@
         // ...and assert that the flag was flipped
         assertTrue(testResources.wasGetDrawableCalled());
     }
-
-    /**
-     * Special Resources class which returns a known Drawable instance from a special ID
-     */
-    private static class TestResources extends Resources {
-        private boolean mGetDrawableCalled;
-
-        private TestResources(Resources res) {
-            super(res.getAssets(), res.getDisplayMetrics(), res.getConfiguration());
-        }
-
-        @Override
-        public Drawable getDrawable(int id) throws NotFoundException {
-            mGetDrawableCalled = true;
-            return super.getDrawable(id);
-        }
-
-        public boolean wasGetDrawableCalled() {
-            return mGetDrawableCalled;
-        }
-    }
-
 }
diff --git a/appcompat/appcompat-resources/src/androidTest/java/androidx/appcompat/widget/VectorEnabledTintResourcesTest.java b/appcompat/appcompat-resources/src/androidTest/java/androidx/appcompat/widget/VectorEnabledTintResourcesTest.java
new file mode 100644
index 0000000..cb02629
--- /dev/null
+++ b/appcompat/appcompat-resources/src/androidTest/java/androidx/appcompat/widget/VectorEnabledTintResourcesTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.appcompat.widget;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.content.res.Resources;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SuppressWarnings("deprecation")
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class VectorEnabledTintResourcesTest {
+    @Rule
+    public final androidx.test.rule.ActivityTestRule<Activity> mActivityTestRule =
+            new androidx.test.rule.ActivityTestRule<>(Activity.class);
+
+    /**
+     * Ensures that TintResources delegates calls to the wrapped Resources object.
+     */
+    @Test
+    public void testVectorEnabledTintResourcesDelegateBackToOriginalResources() {
+        final TestResources testResources =
+                new TestResources(mActivityTestRule.getActivity().getResources());
+
+        // First make sure that the flag is false
+        testResources.resetGetDrawableCalled();
+        assertFalse(testResources.wasGetDrawableCalled());
+
+        // Now wrap in a TintResources instance and get a Drawable
+        final Resources tintResources =
+                new VectorEnabledTintResources(mActivityTestRule.getActivity(), testResources);
+        tintResources.getDrawable(android.R.drawable.ic_delete);
+
+        // ...and assert that the flag was flipped
+        assertTrue(testResources.wasGetDrawableCalled());
+    }
+}
diff --git a/appcompat/appcompat-resources/src/main/java/androidx/appcompat/widget/VectorEnabledTintResources.java b/appcompat/appcompat-resources/src/main/java/androidx/appcompat/widget/VectorEnabledTintResources.java
index b375ac4..6003628 100644
--- a/appcompat/appcompat-resources/src/main/java/androidx/appcompat/widget/VectorEnabledTintResources.java
+++ b/appcompat/appcompat-resources/src/main/java/androidx/appcompat/widget/VectorEnabledTintResources.java
@@ -35,7 +35,7 @@
  * @hide
  */
 @RestrictTo(LIBRARY_GROUP_PREFIX)
-public class VectorEnabledTintResources extends Resources {
+public class VectorEnabledTintResources extends ResourcesWrapper {
     private static boolean sCompatVectorFromResourcesEnabled = false;
 
     public static boolean shouldBeUsed() {
@@ -53,7 +53,7 @@
     @SuppressWarnings("deprecation")
     public VectorEnabledTintResources(@NonNull final Context context,
             @NonNull final Resources res) {
-        super(res.getAssets(), res.getDisplayMetrics(), res.getConfiguration());
+        super(res);
         mContextRef = new WeakReference<>(context);
     }
 
diff --git a/appcompat/appcompat/build.gradle b/appcompat/appcompat/build.gradle
index 2fd0b47..dc4233a 100644
--- a/appcompat/appcompat/build.gradle
+++ b/appcompat/appcompat/build.gradle
@@ -15,12 +15,12 @@
     api(project(":core:core"))
     implementation("androidx.collection:collection:1.0.0")
     api("androidx.cursoradapter:cursoradapter:1.0.0")
-    api("androidx.activity:activity:1.2.0")
-    api("androidx.fragment:fragment:1.3.0")
+    api("androidx.activity:activity:1.2.2")
+    api("androidx.fragment:fragment:1.3.2")
     api(project(":appcompat:appcompat-resources"))
     api("androidx.drawerlayout:drawerlayout:1.0.0")
-    implementation("androidx.lifecycle:lifecycle-runtime:2.3.0")
-    implementation("androidx.lifecycle:lifecycle-viewmodel:2.3.0")
+    implementation("androidx.lifecycle:lifecycle-runtime:2.3.1")
+    implementation("androidx.lifecycle:lifecycle-viewmodel:2.3.1")
     api("androidx.savedstate:savedstate:1.1.0")
 
     androidTestImplementation(KOTLIN_STDLIB)
diff --git a/appcompat/appcompat/src/androidTest/AndroidManifest.xml b/appcompat/appcompat/src/androidTest/AndroidManifest.xml
index c216f56..da2fca5 100644
--- a/appcompat/appcompat/src/androidTest/AndroidManifest.xml
+++ b/appcompat/appcompat/src/androidTest/AndroidManifest.xml
@@ -85,6 +85,14 @@
             android:theme="@style/Theme.AppCompat.Light"/>
 
         <activity
+            android:name="androidx.appcompat.widget.AppCompatSpinnerRotationActivity"
+            android:label="@string/app_compat_spinner_activity"
+            android:theme="@style/Theme.AppCompat.Light"
+            android:screenOrientation="portrait"
+            android:rotationAnimation="jumpcut"
+            android:configChanges="orientation"/>
+
+        <activity
             android:name="androidx.appcompat.widget.AppCompatTextViewActivity"
             android:label="@string/app_compat_text_view_activity"
             android:theme="@style/Theme.TextColors"/>
@@ -165,6 +173,14 @@
             android:theme="@style/Theme.AppCompat.DayNight"/>
 
         <activity
+            android:name="androidx.appcompat.app.NightModeActivityA"
+            android:theme="@style/Theme.AppCompat.DayNight"/>
+
+        <activity
+            android:name="androidx.appcompat.app.NightModeActivityB"
+            android:theme="@style/Theme.AppCompat.DayNight"/>
+
+        <activity
             android:name="androidx.appcompat.app.NightModePreventOverrideConfigActivity"
             android:theme="@style/Theme.AppCompat.DayNight"/>
 
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/AppCompatInflaterPassTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/AppCompatInflaterPassTest.java
index 9b89afd..eb9cfcb 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/AppCompatInflaterPassTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/AppCompatInflaterPassTest.java
@@ -15,8 +15,11 @@
  */
 package androidx.appcompat.app;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 
+import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ScrollView;
 
@@ -28,7 +31,9 @@
 import androidx.appcompat.widget.AppCompatSpinner;
 import androidx.appcompat.widget.AppCompatTextView;
 import androidx.appcompat.widget.AppCompatToggleButton;
+import androidx.core.view.ViewCompat;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 import androidx.test.rule.ActivityTestRule;
 
@@ -98,4 +103,21 @@
                 mContainer.findViewById(R.id.scrollview).getClass());
     }
 
+    @Test
+    @SdkSuppress(minSdkVersion = 19)
+    public void testBackportAccessibilityAttributes() {
+        View view = mContainer.findViewById(R.id.accessibility_heading_view);
+        assertThat(ViewCompat.isAccessibilityHeading(view)).isTrue();
+
+        view = mContainer.findViewById(R.id.accessibility_pane_view);
+        assertThat(ViewCompat.getAccessibilityPaneTitle(view)).isEqualTo("Pane");
+
+        view = mContainer.findViewById(R.id.screen_reader_focusable_view);
+        assertThat(ViewCompat.isScreenReaderFocusable(view)).isTrue();
+
+        view = mContainer.findViewById(R.id.not_accessible_view);
+        assertThat(ViewCompat.isAccessibilityHeading(view)).isFalse();
+        assertThat(ViewCompat.getAccessibilityPaneTitle(view)).isNull();
+        assertThat(ViewCompat.isScreenReaderFocusable(view)).isFalse();
+    }
 }
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeActivity.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeActivity.java
index 0217d41..376c362 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeActivity.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeActivity.java
@@ -27,12 +27,19 @@
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
+/**
+ * An activity with DayNight theme.
+ */
 public class NightModeActivity extends BaseTestActivity {
+    public static final String KEY_TITLE = "title";
+
     private final Semaphore mOnConfigurationChangeSemaphore = new Semaphore(0);
     private final Semaphore mOnDestroySemaphore = new Semaphore(0);
     private final Semaphore mOnCreateSemaphore = new Semaphore(0);
 
     private int mLastNightModeChange = Integer.MIN_VALUE;
+
+    private Configuration mEffectiveConfiguration;
     private Configuration mLastConfigurationChange;
 
     @Override
@@ -49,14 +56,21 @@
     public void onConfigurationChanged(@NonNull Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
 
+        mLastConfigurationChange = new Configuration(newConfig);
+        mEffectiveConfiguration = mLastConfigurationChange;
         mOnConfigurationChangeSemaphore.release();
-        mLastConfigurationChange = newConfig;
     }
 
     @Override
     public void onCreate(Bundle bundle) {
         super.onCreate(bundle);
 
+        String title = getIntent().getStringExtra(KEY_TITLE);
+        if (title != null) {
+            setTitle(title);
+        }
+
+        mEffectiveConfiguration = new Configuration(getResources().getConfiguration());
         mOnCreateSemaphore.release();
     }
 
@@ -74,6 +88,15 @@
         return config;
     }
 
+    /**
+     * @return a copy of the {@link Configuration} from the most recent call to {@link #onCreate} or
+     *         {@link #onConfigurationChanged}, or {@code null} if neither has been called yet
+     */
+    @Nullable
+    Configuration getEffectiveConfiguration() {
+        return mEffectiveConfiguration;
+    }
+
     int getLastNightModeAndReset() {
         final int mode = mLastNightModeChange;
         mLastNightModeChange = Integer.MIN_VALUE;
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeActivityA.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeActivityA.java
new file mode 100644
index 0000000..477e204
--- /dev/null
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeActivityA.java
@@ -0,0 +1,22 @@
+/*
+ * 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.appcompat.app;
+
+/**
+ * An activity with DayNight theme and a unique class name.
+ */
+public class NightModeActivityA extends NightModeActivity {}
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeActivityB.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeActivityB.java
new file mode 100644
index 0000000..e3700bf
--- /dev/null
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeActivityB.java
@@ -0,0 +1,22 @@
+/*
+ * 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.appcompat.app;
+
+/**
+ * An activity with DayNight theme and a unique class name.
+ */
+public class NightModeActivityB extends NightModeActivity {}
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeStackedHandlingTestCase.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeStackedHandlingTestCase.kt
new file mode 100644
index 0000000..62f9285
--- /dev/null
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeStackedHandlingTestCase.kt
@@ -0,0 +1,161 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("DEPRECATION")
+
+package androidx.appcompat.app
+
+import android.app.Activity
+import android.app.Instrumentation
+import android.app.Instrumentation.ActivityMonitor
+import android.content.Intent
+import android.content.res.Configuration
+import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
+import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
+import androidx.appcompat.testutils.NightModeUtils
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import junit.framework.Assert.assertNotNull
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+public class NightModeStackedHandlingTestCase {
+
+    /**
+     * Regression test for the following scenario:
+     *
+     * If you have a stack of activities which includes one with android:configChanges="uiMode"
+     * and you call AppCompatDelegate.setDefaultNightMode it can cause other activities to not be
+     * recreated.
+     *
+     * Eg:
+     * - Activity A DOESN'T intercept uiMode config changes in manifest
+     * - Activity B DOESN'T intercept uiMode config changes in manifest
+     * - Activity C DOES
+     *
+     * Here is your stack : A > B > C (C on top)
+     *
+     * Call AppCompatDelegate.setDefaultNightMode with a new mode on activity C. Activity C
+     * receives the change in onConfigurationChanged but there is a good chance that activity A
+     * and/or B were not recreated.
+     */
+    @Test
+    @SdkSuppress(minSdkVersion = 17)
+    public fun testDefaultNightModeWithStackedActivities() {
+        val instr = InstrumentationRegistry.getInstrumentation()
+        val result = Instrumentation.ActivityResult(0, Intent())
+        val monitorA = ActivityMonitor(NightModeActivityA::class.java.name, result, false)
+        val monitorB = ActivityMonitor(NightModeActivityB::class.java.name, result, false)
+        val monitorC = ActivityMonitor(
+            NightModeUiModeConfigChangesActivity::class.java.name,
+            result, false
+        )
+        instr.addMonitor(monitorA)
+        instr.addMonitor(monitorB)
+        instr.addMonitor(monitorC)
+
+        instr.runOnMainSync {
+            AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_NO)
+        }
+
+        // Start activity A.
+        instr.startActivitySync(
+            Intent(instr.context, NightModeActivityA::class.java).apply {
+                addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                putExtra(NightModeActivity.KEY_TITLE, "A")
+            }
+        )
+
+        // From activity A, start activity B.
+        var activityA = monitorA.waitForActivityWithTimeout(3000) as NightModeActivity
+        assertNotNull(activityA)
+        activityA.startActivity(
+            Intent(instr.context, NightModeActivityB::class.java).apply {
+                putExtra(NightModeActivity.KEY_TITLE, "B")
+            }
+        )
+
+        // From activity B, start activity C.
+        val activityB = monitorB.waitForActivityWithTimeout(3000) as NightModeActivity
+        assertNotNull(activityB)
+        activityB.startActivity(
+            Intent(instr.context, NightModeUiModeConfigChangesActivity::class.java).apply {
+                putExtra(NightModeActivity.KEY_TITLE, "C")
+            }
+        )
+
+        // Toggle default night mode.
+        val activityC = monitorC.waitForActivityWithTimeout(3000) as NightModeActivity
+        assertNotNull(activityC)
+        activityC.runOnUiThread {
+            AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_YES)
+        }
+
+        // Activity C should receive a configuration change.
+        activityC.expectOnConfigurationChange(3000)
+
+        // Activities A and B should recreate().
+        val activityA2 = expectRecreate(monitorA, activityA) as NightModeActivity
+        val activityB2 = expectRecreate(monitorB, activityB) as NightModeActivity
+
+        // Activity C should have received a night mode configuration change.
+        activityC.runOnUiThread {
+            NightModeUtils.assertConfigurationNightModeEquals(
+                "Activity A's effective configuration has night mode set",
+                Configuration.UI_MODE_NIGHT_YES,
+                activityC.effectiveConfiguration!!
+            )
+        }
+
+        // Activity A should have been recreated in night mode.
+        activityA2.runOnUiThread {
+            NightModeUtils.assertConfigurationNightModeEquals(
+                "Activity A's effective configuration has night mode set",
+                Configuration.UI_MODE_NIGHT_YES,
+                activityA2.effectiveConfiguration!!
+            )
+        }
+
+        // Activity B should have been recreated in night mode.
+        activityB2.runOnUiThread {
+            NightModeUtils.assertConfigurationNightModeEquals(
+                "Activity B's effective configuration has night mode set",
+                Configuration.UI_MODE_NIGHT_YES,
+                activityB2.effectiveConfiguration!!
+            )
+        }
+    }
+
+    fun expectRecreate(monitor: ActivityMonitor, activity: Activity): Activity {
+        // The documentation says "Block until an Activity is created that matches this monitor."
+        // This statement is true, but there are some other true statements like: "Block until an
+        // Activity is destroyed" or "Block until an Activity is resumed"...
+        var activityResult: Activity?
+        synchronized(monitor) {
+            do {
+                // this call will release synchronization monitor's monitor
+                activityResult = monitor.waitForActivityWithTimeout(3000)
+            } while (activityResult != null && activityResult == activity)
+        }
+
+        assertNotNull("Recreated activity " + activity.title, activityResult)
+        return activityResult!!
+    }
+}
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeUiModeConfigChangesActivity.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeUiModeConfigChangesActivity.java
index 5498d4b..a418dad 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeUiModeConfigChangesActivity.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/app/NightModeUiModeConfigChangesActivity.java
@@ -16,4 +16,7 @@
 
 package androidx.appcompat.app;
 
+/**
+ * An activity with DayNight theme that handles uiMode configuration changes.
+ */
 public class NightModeUiModeConfigChangesActivity extends NightModeActivity {}
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/testutils/NightModeUtils.kt b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/testutils/NightModeUtils.kt
index 5f49760..1fb9286 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/testutils/NightModeUtils.kt
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/testutils/NightModeUtils.kt
@@ -52,6 +52,19 @@
         context: Context
     ) {
         assertConfigurationNightModeEquals(
+            null,
+            expectedNightMode,
+            context
+        )
+    }
+
+    fun assertConfigurationNightModeEquals(
+        message: String?,
+        expectedNightMode: Int,
+        context: Context
+    ) {
+        assertConfigurationNightModeEquals(
+            message,
             expectedNightMode,
             context.resources.configuration
         )
@@ -61,7 +74,20 @@
         expectedNightMode: Int,
         configuration: Configuration
     ) {
+        assertConfigurationNightModeEquals(
+            null,
+            expectedNightMode,
+            configuration
+        )
+    }
+
+    fun assertConfigurationNightModeEquals(
+        message: String?,
+        expectedNightMode: Int,
+        configuration: Configuration
+    ) {
         assertEquals(
+            message,
             expectedNightMode.toLong(),
             (configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK).toLong()
         )
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerRotationActivity.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerRotationActivity.java
new file mode 100644
index 0000000..17c3891
--- /dev/null
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerRotationActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.appcompat.widget;
+
+import androidx.appcompat.test.R;
+import androidx.appcompat.testutils.BaseTestActivity;
+
+public class AppCompatSpinnerRotationActivity extends BaseTestActivity {
+    @Override
+    protected int getContentViewLayoutResId() {
+        return R.layout.appcompat_spinner_rotation_activity;
+    }
+}
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerRotationTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerRotationTest.java
new file mode 100644
index 0000000..505a8cc
--- /dev/null
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerRotationTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+package androidx.appcompat.widget;
+
+import static androidx.appcompat.testutils.TestUtilsActions.rotateScreenOrientation;
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+
+import android.app.Instrumentation;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+import androidx.annotation.IdRes;
+import androidx.appcompat.test.R;
+import androidx.test.espresso.UiController;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.testutils.PollingCheck;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Rotation tests for {@link AppCompatSpinner}
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class AppCompatSpinnerRotationTest {
+    private Instrumentation mInstrumentation;
+
+    @Rule
+    public final ActivityTestRule<AppCompatSpinnerRotationActivity> mActivityTestRule;
+
+    protected AppCompatSpinnerRotationActivity mActivity;
+    protected UiController mUiController;
+
+    public AppCompatSpinnerRotationTest() {
+        mActivityTestRule = new ActivityTestRule<>(AppCompatSpinnerRotationActivity.class);
+    }
+
+    @Before
+    public void setUp() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mActivity = mActivityTestRule.getActivity();
+    }
+
+    @Test
+    public void testChangeOrientationDialogPopupPersists() {
+        verifyChangeOrientationPopupPersists(R.id.spinner_dialog_popup);
+    }
+
+    @Test
+    public void testChangeOrientationDropdownPopupPersists() {
+        verifyChangeOrientationPopupPersists(R.id.spinner_dropdown_popup);
+    }
+
+    private void verifyChangeOrientationPopupPersists(@IdRes int spinnerId) {
+        // Does the device support both orientations?
+        PackageManager pm = mActivity.getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT)
+                || !pm.hasSystemFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE)) {
+            // Can't rotate - the screen might be locked to one orientation
+            // or something like TV that doesn't support rotation at all.
+            return;
+        }
+
+        onView(withId(spinnerId)).perform(click());
+        // Wait until the popup is showing
+        waitUntilPopupIsShown((AppCompatSpinner) mActivity.findViewById(spinnerId));
+
+        // Use ActivityMonitor so that we can get the Activity instance after it has been
+        // recreated when the rotation request completes
+        Instrumentation.ActivityMonitor monitor =
+                new Instrumentation.ActivityMonitor(mActivity.getClass().getName(), null, false);
+        mInstrumentation.addMonitor(monitor);
+
+        // Request screen rotation
+        onView(isRoot()).perform(rotateScreenOrientation(mActivity));
+
+        mActivity = (AppCompatSpinnerRotationActivity) mInstrumentation.waitForMonitorWithTimeout(
+                monitor, 5000);
+        if (mActivity == null) {
+            // Device orientation is locked and screen can't be rotated
+            Log.d("AppCompatSpinnerRotationTest", "Failed to recreate() activity after rotating "
+                    + "the screen! Assuming screen orientation is locked and aborting test.");
+            return;
+        }
+        mInstrumentation.waitForIdleSync();
+
+        // Now we can get the new (post-rotation) instance of our spinner nd check that it's
+        // showing the popup
+        waitUntilPopupIsShown((AppCompatSpinner) mActivity.findViewById(spinnerId));
+    }
+
+    private void waitUntilPopupIsShown(final AppCompatSpinner spinner) {
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return spinner.getInternalPopup().isShowing();
+            }
+        });
+    }
+}
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerTest.java
index 3c16903..cde0d75 100644
--- a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerTest.java
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatSpinnerTest.java
@@ -15,7 +15,6 @@
  */
 package androidx.appcompat.widget;
 
-import static androidx.appcompat.testutils.TestUtilsActions.setScreenOrientation;
 import static androidx.appcompat.testutils.TestUtilsMatchers.asViewMatcher;
 import static androidx.appcompat.testutils.TestUtilsMatchers.hasChild;
 import static androidx.appcompat.testutils.TestUtilsMatchers.isCombinedBackground;
@@ -25,17 +24,14 @@
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
 import static androidx.test.espresso.matcher.RootMatchers.isPlatformPopup;
 import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
-import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
 import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
 
 import android.app.Instrumentation;
-import android.content.pm.ActivityInfo;
 import android.content.res.Resources;
 import android.view.View;
 import android.view.ViewGroup;
@@ -192,39 +188,6 @@
     }
 
     @Test
-    public void testChangeOrientationDialogPopupPersists() {
-        verifyChangeOrientationPopupPersists(R.id.spinner_dialog_popup);
-    }
-
-    @Test
-    public void testChangeOrientationDropdownPopupPersists() {
-        verifyChangeOrientationPopupPersists(R.id.spinner_dropdown_popup);
-    }
-
-    private void verifyChangeOrientationPopupPersists(@IdRes int spinnerId) {
-        onView(withId(spinnerId)).perform(click());
-        // Wait until the popup is showing
-        waitUntilPopupIsShown((AppCompatSpinner) mActivity.findViewById(spinnerId));
-
-        // Use ActivityMonitor so that we can get the Activity instance after it has been
-        // recreated when the rotation request completes
-        Instrumentation.ActivityMonitor monitor =
-                new Instrumentation.ActivityMonitor(mActivity.getClass().getName(), null, false);
-        mInstrumentation.addMonitor(monitor);
-
-        onView(isRoot()).perform(
-                setScreenOrientation(mActivity, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE));
-
-        mActivity = (AppCompatSpinnerActivity) mInstrumentation.waitForMonitor(monitor);
-        mInstrumentation.waitForIdleSync();
-
-        // Now we can get the new (post-rotation) instance of our spinner
-        AppCompatSpinner newSpinner = mActivity.findViewById(spinnerId);
-        // And check that it's showing the popup
-        assertTrue(newSpinner.getInternalPopup().isShowing());
-    }
-
-    @Test
     @FlakyTest
     public void testSlowScroll() {
         final AppCompatSpinner spinner = mContainer
diff --git a/appcompat/appcompat/src/androidTest/res/layout/appcompat_inflater_activity.xml b/appcompat/appcompat/src/androidTest/res/layout/appcompat_inflater_activity.xml
index 07a7f82..033f5ab 100644
--- a/appcompat/appcompat/src/androidTest/res/layout/appcompat_inflater_activity.xml
+++ b/appcompat/appcompat/src/androidTest/res/layout/appcompat_inflater_activity.xml
@@ -85,6 +85,28 @@
             android:layout_width="match_parent"
             android:layout_height="100dp" />
 
+        <FrameLayout
+            android:id="@+id/accessibility_heading_view"
+            android:accessibilityHeading="true"
+            android:layout_width="match_parent"
+            android:layout_height="10dp" />
+
+        <FrameLayout
+            android:id="@+id/accessibility_pane_view"
+            android:accessibilityPaneTitle="Pane"
+            android:layout_width="match_parent"
+            android:layout_height="10dp" />
+
+        <FrameLayout
+            android:id="@+id/screen_reader_focusable_view"
+            android:screenReaderFocusable="true"
+            android:layout_width="match_parent"
+            android:layout_height="10dp" />
+
+        <FrameLayout
+            android:id="@+id/not_accessible_view"
+            android:layout_width="match_parent"
+            android:layout_height="10dp" />
     </LinearLayout>
 
 </ScrollView>
diff --git a/appcompat/appcompat/src/androidTest/res/layout/appcompat_spinner_rotation_activity.xml b/appcompat/appcompat/src/androidTest/res/layout/appcompat_spinner_rotation_activity.xml
new file mode 100644
index 0000000..e5ee291
--- /dev/null
+++ b/appcompat/appcompat/src/androidTest/res/layout/appcompat_spinner_rotation_activity.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <View
+            android:id="@+id/for_focus"
+            android:layout_width="match_parent"
+            android:layout_height="4dp"
+            android:focusable="true"/>
+
+        <androidx.appcompat.widget.AppCompatSpinner
+            android:id="@+id/spinner_dialog_popup"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:entries="@array/planets_array"
+            android:spinnerMode="dialog"
+            android:focusable="false" />
+
+        <androidx.appcompat.widget.AppCompatSpinner
+            android:id="@+id/spinner_dropdown_popup"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:entries="@array/planets_array"
+            android:focusable="false"
+            android:spinnerMode="dropdown" />
+
+    </LinearLayout>
+
+</ScrollView>
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java b/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
index 90f4b18..ff1d0ad 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java
@@ -249,6 +249,12 @@
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     boolean mIsDestroyed;
 
+    /**
+     * The configuration from the most recent call to either onConfigurationChanged or onCreate.
+     * May be null neither method has been called yet.
+     */
+    private Configuration mEffectiveConfiguration;
+
     @NightMode
     private int mLocalNightMode = MODE_NIGHT_UNSPECIFIED;
 
@@ -521,6 +527,7 @@
             addActiveDelegate(this);
         }
 
+        mEffectiveConfiguration = new Configuration(mContext.getResources().getConfiguration());
         mCreated = true;
     }
 
@@ -650,6 +657,10 @@
         // Make sure that the DrawableManager knows about the new config
         AppCompatDrawableManager.get().onConfigurationChanged(mContext);
 
+        // Cache the last-seen configuration before calling applyDayNight, since applyDayNight
+        // inspects the last-seen configuration. Otherwise, we'll recurse back to this method.
+        mEffectiveConfiguration = new Configuration(mContext.getResources().getConfiguration());
+
         // Re-apply Day/Night with the new configuration but disable recreations. Since this
         // configuration change has only just happened we can safely just update the resources now
         applyDayNight(false);
@@ -2509,7 +2520,9 @@
                 createOverrideConfigurationForDayNight(mContext, mode, null);
 
         final boolean activityHandlingUiMode = isActivityManifestHandlingUiMode();
-        final int currentNightMode = mContext.getResources().getConfiguration().uiMode
+        final Configuration currentConfiguration = mEffectiveConfiguration == null
+                ? mContext.getResources().getConfiguration() : mEffectiveConfiguration;
+        final int currentNightMode = currentConfiguration.uiMode
                 & Configuration.UI_MODE_NIGHT_MASK;
         final int newNightMode = overrideConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
 
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatViewInflater.java b/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatViewInflater.java
index 32bce8c..d874545 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatViewInflater.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatViewInflater.java
@@ -75,6 +75,12 @@
     private static final Class<?>[] sConstructorSignature = new Class<?>[]{
             Context.class, AttributeSet.class};
     private static final int[] sOnClickAttrs = new int[]{android.R.attr.onClick};
+    private static final int[] sAccessibilityHeading =
+            new int[]{android.R.attr.accessibilityHeading};
+    private static final int[] sAccessibilityPaneTitle =
+            new int[]{android.R.attr.accessibilityPaneTitle};
+    private static final int[] sScreenReaderFocusable =
+            new int[]{android.R.attr.screenReaderFocusable};
 
     private static final String[] sClassPrefixList = {
             "android.widget.",
@@ -184,6 +190,7 @@
         if (view != null) {
             // If we have created a view, check its android:onClick
             checkOnClickListener(view, attrs);
+            backportAccessibilityAttributes(context, view, attrs);
         }
 
         return view;
@@ -383,6 +390,31 @@
         return context;
     }
 
+    private void backportAccessibilityAttributes(@NonNull Context context, @NonNull View view,
+            @NonNull AttributeSet attrs) {
+        if (Build.VERSION.SDK_INT < 19 || Build.VERSION.SDK_INT > 28) {
+            return;
+        }
+
+        TypedArray a = context.obtainStyledAttributes(attrs, sAccessibilityHeading);
+        if (a.hasValue(0)) {
+            ViewCompat.setAccessibilityHeading(view, a.getBoolean(0, false));
+        }
+        a.recycle();
+
+        a = context.obtainStyledAttributes(attrs, sAccessibilityPaneTitle);
+        if (a.hasValue(0)) {
+            ViewCompat.setAccessibilityPaneTitle(view, a.getString(0));
+        }
+        a.recycle();
+
+        a = context.obtainStyledAttributes(attrs, sScreenReaderFocusable);
+        if (a.hasValue(0)) {
+            ViewCompat.setScreenReaderFocusable(view, a.getBoolean(0, false));
+        }
+        a.recycle();
+    }
+
     /**
      * An implementation of OnClickListener that attempts to lazily load a
      * named click handling method from a parent or ancestor context.
diff --git a/autofill/autofill/api/current.txt b/autofill/autofill/api/current.txt
index 96f419f..988cdcf 100644
--- a/autofill/autofill/api/current.txt
+++ b/autofill/autofill/api/current.txt
@@ -14,10 +14,12 @@
     field public static final String AUTOFILL_HINT_CREDIT_CARD_NUMBER = "creditCardNumber";
     field public static final String AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = "creditCardSecurityCode";
     field public static final String AUTOFILL_HINT_EMAIL_ADDRESS = "emailAddress";
+    field public static final String AUTOFILL_HINT_EMAIL_OTP = "emailOTPCode";
     field public static final String AUTOFILL_HINT_GENDER = "gender";
     field @Deprecated public static final String AUTOFILL_HINT_NAME = "name";
     field public static final String AUTOFILL_HINT_NEW_PASSWORD = "newPassword";
     field public static final String AUTOFILL_HINT_NEW_USERNAME = "newUsername";
+    field public static final String AUTOFILL_HINT_NOT_APPLICABLE = "notApplicable";
     field public static final String AUTOFILL_HINT_PASSWORD = "password";
     field public static final String AUTOFILL_HINT_PERSON_NAME = "personName";
     field public static final String AUTOFILL_HINT_PERSON_NAME_FAMILY = "personFamilyName";
@@ -32,15 +34,21 @@
     field public static final String AUTOFILL_HINT_PHONE_NUMBER = "phoneNumber";
     field public static final String AUTOFILL_HINT_PHONE_NUMBER_DEVICE = "phoneNumberDevice";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS = "postalAddress";
+    field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_APT_NUMBER = "aptNumber";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_COUNTRY = "addressCountry";
+    field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_DEPENDENT_LOCALITY = "dependentLocality";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_ADDRESS = "extendedAddress";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_POSTAL_CODE = "extendedPostalCode";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_LOCALITY = "addressLocality";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_REGION = "addressRegion";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_STREET_ADDRESS = "streetAddress";
     field public static final String AUTOFILL_HINT_POSTAL_CODE = "postalCode";
+    field public static final String AUTOFILL_HINT_PROMO_CODE = "promoCode";
     field public static final String AUTOFILL_HINT_SMS_OTP = "smsOTPCode";
+    field public static final String AUTOFILL_HINT_TFA_APP_OTP = "tfaAppOTPCode";
+    field public static final String AUTOFILL_HINT_UPI_VPA = "upiVirtualPaymentAddress";
     field public static final String AUTOFILL_HINT_USERNAME = "username";
+    field public static final String AUTOFILL_HINT_WIFI_PASSWORD = "wifiPassword";
   }
 
 }
diff --git a/autofill/autofill/api/public_plus_experimental_current.txt b/autofill/autofill/api/public_plus_experimental_current.txt
index 96f419f..988cdcf 100644
--- a/autofill/autofill/api/public_plus_experimental_current.txt
+++ b/autofill/autofill/api/public_plus_experimental_current.txt
@@ -14,10 +14,12 @@
     field public static final String AUTOFILL_HINT_CREDIT_CARD_NUMBER = "creditCardNumber";
     field public static final String AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = "creditCardSecurityCode";
     field public static final String AUTOFILL_HINT_EMAIL_ADDRESS = "emailAddress";
+    field public static final String AUTOFILL_HINT_EMAIL_OTP = "emailOTPCode";
     field public static final String AUTOFILL_HINT_GENDER = "gender";
     field @Deprecated public static final String AUTOFILL_HINT_NAME = "name";
     field public static final String AUTOFILL_HINT_NEW_PASSWORD = "newPassword";
     field public static final String AUTOFILL_HINT_NEW_USERNAME = "newUsername";
+    field public static final String AUTOFILL_HINT_NOT_APPLICABLE = "notApplicable";
     field public static final String AUTOFILL_HINT_PASSWORD = "password";
     field public static final String AUTOFILL_HINT_PERSON_NAME = "personName";
     field public static final String AUTOFILL_HINT_PERSON_NAME_FAMILY = "personFamilyName";
@@ -32,15 +34,21 @@
     field public static final String AUTOFILL_HINT_PHONE_NUMBER = "phoneNumber";
     field public static final String AUTOFILL_HINT_PHONE_NUMBER_DEVICE = "phoneNumberDevice";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS = "postalAddress";
+    field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_APT_NUMBER = "aptNumber";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_COUNTRY = "addressCountry";
+    field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_DEPENDENT_LOCALITY = "dependentLocality";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_ADDRESS = "extendedAddress";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_POSTAL_CODE = "extendedPostalCode";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_LOCALITY = "addressLocality";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_REGION = "addressRegion";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_STREET_ADDRESS = "streetAddress";
     field public static final String AUTOFILL_HINT_POSTAL_CODE = "postalCode";
+    field public static final String AUTOFILL_HINT_PROMO_CODE = "promoCode";
     field public static final String AUTOFILL_HINT_SMS_OTP = "smsOTPCode";
+    field public static final String AUTOFILL_HINT_TFA_APP_OTP = "tfaAppOTPCode";
+    field public static final String AUTOFILL_HINT_UPI_VPA = "upiVirtualPaymentAddress";
     field public static final String AUTOFILL_HINT_USERNAME = "username";
+    field public static final String AUTOFILL_HINT_WIFI_PASSWORD = "wifiPassword";
   }
 
 }
diff --git a/autofill/autofill/api/restricted_current.txt b/autofill/autofill/api/restricted_current.txt
index 608378c..60c1d8d 100644
--- a/autofill/autofill/api/restricted_current.txt
+++ b/autofill/autofill/api/restricted_current.txt
@@ -14,10 +14,12 @@
     field public static final String AUTOFILL_HINT_CREDIT_CARD_NUMBER = "creditCardNumber";
     field public static final String AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = "creditCardSecurityCode";
     field public static final String AUTOFILL_HINT_EMAIL_ADDRESS = "emailAddress";
+    field public static final String AUTOFILL_HINT_EMAIL_OTP = "emailOTPCode";
     field public static final String AUTOFILL_HINT_GENDER = "gender";
     field @Deprecated public static final String AUTOFILL_HINT_NAME = "name";
     field public static final String AUTOFILL_HINT_NEW_PASSWORD = "newPassword";
     field public static final String AUTOFILL_HINT_NEW_USERNAME = "newUsername";
+    field public static final String AUTOFILL_HINT_NOT_APPLICABLE = "notApplicable";
     field public static final String AUTOFILL_HINT_PASSWORD = "password";
     field public static final String AUTOFILL_HINT_PERSON_NAME = "personName";
     field public static final String AUTOFILL_HINT_PERSON_NAME_FAMILY = "personFamilyName";
@@ -32,15 +34,21 @@
     field public static final String AUTOFILL_HINT_PHONE_NUMBER = "phoneNumber";
     field public static final String AUTOFILL_HINT_PHONE_NUMBER_DEVICE = "phoneNumberDevice";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS = "postalAddress";
+    field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_APT_NUMBER = "aptNumber";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_COUNTRY = "addressCountry";
+    field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_DEPENDENT_LOCALITY = "dependentLocality";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_ADDRESS = "extendedAddress";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_EXTENDED_POSTAL_CODE = "extendedPostalCode";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_LOCALITY = "addressLocality";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_REGION = "addressRegion";
     field public static final String AUTOFILL_HINT_POSTAL_ADDRESS_STREET_ADDRESS = "streetAddress";
     field public static final String AUTOFILL_HINT_POSTAL_CODE = "postalCode";
+    field public static final String AUTOFILL_HINT_PROMO_CODE = "promoCode";
     field public static final String AUTOFILL_HINT_SMS_OTP = "smsOTPCode";
+    field public static final String AUTOFILL_HINT_TFA_APP_OTP = "tfaAppOTPCode";
+    field public static final String AUTOFILL_HINT_UPI_VPA = "upiVirtualPaymentAddress";
     field public static final String AUTOFILL_HINT_USERNAME = "username";
+    field public static final String AUTOFILL_HINT_WIFI_PASSWORD = "wifiPassword";
   }
 
 }
diff --git a/autofill/autofill/src/main/java/androidx/autofill/HintConstants.java b/autofill/autofill/src/main/java/androidx/autofill/HintConstants.java
index e0ce904..c72cd28 100644
--- a/autofill/autofill/src/main/java/androidx/autofill/HintConstants.java
+++ b/autofill/autofill/src/main/java/androidx/autofill/HintConstants.java
@@ -83,6 +83,18 @@
     public static final String AUTOFILL_HINT_PASSWORD = "password";
 
     /**
+     * Hint indicating that this view can be autofilled with a wifi password.
+     *
+     * <p>Can be used with either {@link android.view.View#setAutofillHints(String[])} or <a
+     * href="#attr_android:autofillHint">{@code android:autofillHint}</a> (in which case the value
+     * should be <code>{@value #AUTOFILL_HINT_WIFI_PASSWORD}</code>).
+     *
+     * <p>See {@link android.view.View#setAutofillHints(String...)} for more info about autofill
+     * hints.
+     */
+    public static final String AUTOFILL_HINT_WIFI_PASSWORD = "wifiPassword";
+
+    /**
      * Hint indicating that this view can be autofilled with a phone number.
      *
      * <p>Can be used with either {@link android.view.View#setAutofillHints(String[])} or <a
@@ -322,6 +334,32 @@
             "extendedPostalCode";
 
     /**
+     * Hint indicating that this view can be autofilled with an apartment/room/suite number.
+     *
+     * <p>Can be used with either {@link android.view.View#setAutofillHints(String[])} or <a
+     * href="#attr_android:autofillHint">{@code android:autofillHint}</a> (in which case the value
+     * should be <code>{@value #AUTOFILL_HINT_POSTAL_ADDRESS_APT_NUMBER}</code>).
+     *
+     * <p>See {@link android.view.View#setAutofillHints(String...)} for more info about autofill
+     * hints.
+     */
+    public static final String AUTOFILL_HINT_POSTAL_ADDRESS_APT_NUMBER = "aptNumber";
+
+    /**
+     * Hint indicating that this view can be autofilled with a dependent locality i.e.
+     * district/locality division/postal division/suburb etc.
+     *
+     * <p>Can be used with either {@link android.view.View#setAutofillHints(String[])} or <a
+     * href="#attr_android:autofillHint">{@code android:autofillHint}</a> (in which case the value
+     * should be <code>{@value #AUTOFILL_HINT_POSTAL_ADDRESS_DEPENDENT_LOCALITY}</code>).
+     *
+     * <p>See {@link android.view.View#setAutofillHints(String...)} for more info about autofill
+     * hints.
+     */
+    public static final String AUTOFILL_HINT_POSTAL_ADDRESS_DEPENDENT_LOCALITY =
+            "dependentLocality";
+
+    /**
      * Hint indicating that this view can be autofilled with a person's full name.
      *
      * <p>Can be used with either {@link android.view.View#setAutofillHints(String[])} or <a
@@ -580,4 +618,65 @@
         Preconditions.checkArgumentInRange(characterPosition, 1, 8, "characterPosition");
         return ("smsOTPCode" + characterPosition).intern();
     }
+
+    /**
+     * Hint indicating that this view can be autofilled with an Email One Time Password (OTP).
+     *
+     * <p>Can be used with either {@link android.view.View#setAutofillHints(String[])} or <a
+     * href="#attr_android:autofillHint">{@code android:autofillHint}</a> (in which case the value
+     * should be <code>{@value #AUTOFILL_HINT_EMAIL_OTP}</code>).
+     *
+     * <p>See {@link android.view.View#setAutofillHints(String...)} for more info about autofill
+     * hints.
+     */
+    public static final String AUTOFILL_HINT_EMAIL_OTP = "emailOTPCode";
+
+    /**
+     * Hint indicating that this view can be autofilled with an Time-Based One Time Password (OTP)
+     * generated by 2FA apps.
+     *
+     * <p>Can be used with either {@link android.view.View#setAutofillHints(String[])} or <a
+     * href="#attr_android:autofillHint">{@code android:autofillHint}</a> (in which case the value
+     * should be <code>{@value #AUTOFILL_HINT_TFA_APP_OTP}</code>).
+     *
+     * <p>See {@link android.view.View#setAutofillHints(String...)} for more info about autofill
+     * hints.
+     */
+    public static final String AUTOFILL_HINT_TFA_APP_OTP = "tfaAppOTPCode";
+
+    /**
+     * Hint indicating that this view is not eligible for autofill.
+     *
+     * <p>Can be used with either {@link android.view.View#setAutofillHints(String[])} or <a
+     * href="#attr_android:autofillHint">{@code android:autofillHint}</a> (in which case the value
+     * should be <code>{@value #AUTOFILL_HINT_NOT_APPLICABLE}</code>).
+     *
+     * <p>See {@link android.view.View#setAutofillHints(String...)} for more info about autofill
+     * hints.
+     */
+    public static final String AUTOFILL_HINT_NOT_APPLICABLE = "notApplicable";
+
+    /**
+     * Hint indicating that this view can be autofilled with a promo/coupon code.
+     *
+     * <p>Can be used with either {@link android.view.View#setAutofillHints(String[])} or <a
+     * href="#attr_android:autofillHint">{@code android:autofillHint}</a> (in which case the value
+     * should be <code>{@value #AUTOFILL_HINT_PROMO_CODE}</code>).
+     *
+     * <p>See {@link android.view.View#setAutofillHints(String...)} for more info about autofill
+     * hints.
+     */
+    public static final String AUTOFILL_HINT_PROMO_CODE = "promoCode";
+
+    /**
+     * Hint indicating that this view can be autofilled with an UPI Virtual Payment Address.
+     *
+     * <p>Can be used with either {@link android.view.View#setAutofillHints(String[])} or <a
+     * href="#attr_android:autofillHint">{@code android:autofillHint}</a> (in which case the value
+     * should be <code>{@value #AUTOFILL_HINT_UPI_VPA}</code>).
+     *
+     * <p>See {@link android.view.View#setAutofillHints(String...)} for more info about autofill
+     * hints.
+     */
+    public static final String AUTOFILL_HINT_UPI_VPA = "upiVirtualPaymentAddress";
 }
diff --git a/benchmark/common/lint-baseline.xml b/benchmark/common/lint-baseline.xml
index d929aa6..a6e93ad 100644
--- a/benchmark/common/lint-baseline.xml
+++ b/benchmark/common/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
+<issues format="5" by="lint 4.2.0-beta04" client="gradle" variant="debug" version="4.2.0-beta04">
 
     <issue
         id="BanSynchronizedMethods"
@@ -111,15 +111,4 @@
             column="30"/>
     </issue>
 
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 21, the call containing class androidx.benchmark.ProfilerKt is not annotated with @RequiresApi(x) where x is at least 21. Either annotate the containing class with at least @RequiresApi(21) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(21)."
-        errorLine1="        Debug.startMethodTracingSampling(path, bufferSize, Arguments.profilerSampleFrequency)"
-        errorLine2="              ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/main/java/androidx/benchmark/Profiler.kt"
-            line="99"
-            column="15"/>
-    </issue>
-
 </issues>
diff --git a/benchmark/common/src/androidTest/java/androidx/benchmark/OutputsTest.kt b/benchmark/common/src/androidTest/java/androidx/benchmark/OutputsTest.kt
new file mode 100644
index 0000000..ae83060
--- /dev/null
+++ b/benchmark/common/src/androidTest/java/androidx/benchmark/OutputsTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.benchmark
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.File
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+public class OutputsTest {
+    private val outputs: MutableList<String> = mutableListOf()
+
+    @Before
+    public fun setUp() {
+        outputs.addAll(
+            // Don't add the / prefix.
+            listOf(
+                "foo/a.txt",
+                "foo/b.txt",
+                "foo/bar/a.txt",
+                "foo/bar/baz/a.txt",
+            )
+        )
+    }
+
+    @Test
+    public fun testRelativePaths_usesIntendedOutputDirectory() {
+        assertRelativePaths(Outputs.outputDirectory, outputs)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 30, maxSdkVersion = 30)
+    public fun testRelativePaths_usesDirectoryUsableByAppAndShell() {
+        assertRelativePaths(Outputs.dirUsableByAppAndShell, outputs)
+    }
+
+    private fun assertRelativePaths(base: File, paths: List<String>) {
+        val basePath = base.absolutePath
+        val relativePaths = paths.map { Outputs.relativePathFor(File(base, it).absolutePath) }
+        relativePaths.forEach { path ->
+            assertFalse(path.startsWith("/"), "$path cannot start with a `/`.")
+            assertFalse(
+                path.startsWith(basePath),
+                "Invalid relative path ($path), Base ($basePath)."
+            )
+        }
+
+        for ((path, relativePath) in paths.zip(relativePaths)) {
+            assertEquals(path, relativePath, "$path != $relativePath")
+        }
+    }
+}
diff --git a/benchmark/common/src/main/java/androidx/benchmark/Outputs.kt b/benchmark/common/src/main/java/androidx/benchmark/Outputs.kt
index ae2b62c..23205d8 100644
--- a/benchmark/common/src/main/java/androidx/benchmark/Outputs.kt
+++ b/benchmark/common/src/main/java/androidx/benchmark/Outputs.kt
@@ -31,7 +31,7 @@
 public object Outputs {
 
     /**
-     * The output directory that the developer wants us to use.
+     * The intended output directory that respects the `additionalTestOutputDir`.
      */
     public val outputDirectory: File
 
@@ -81,8 +81,10 @@
         reportOnRunEndOnly: Boolean = false,
         block: (file: File) -> Unit,
     ): String {
-        val override = Build.VERSION.SDK_INT == Build.VERSION_CODES.R
-        // We override the `additionalTestOutputDir` argument on R.
+        // We need to copy files over anytime `dirUsableByAppAndShell` is different from
+        // `outputDirectory`.
+        val override = dirUsableByAppAndShell != outputDirectory
+        // We override the `additionalTestOutputDir` argument.
         // Context: b/181601156
         val file = File(dirUsableByAppAndShell, fileName)
         try {
@@ -93,22 +95,21 @@
                 // This respects the `additionalTestOutputDir` argument.
                 val actualOutputDirectory = outputDirectory
                 destination = File(actualOutputDirectory, fileName)
-                if (file != destination) {
-                    try {
-                        destination.mkdirs()
-                        file.copyTo(destination, overwrite = true)
-                    } catch (exception: Throwable) {
-                        // This can happen when `additionalTestOutputDir` being passed in cannot
-                        // be written to. The shell does not have permissions to do the necessary
-                        // setup, and this can cause `adb pull` to fail.
-                        val message = """
-                            Unable to copy files to ${destination.absolutePath}.
-                            Please pull the Macrobenchmark results manually by using:
-                            adb pull ${file.absolutePath}
-                        """.trimIndent()
-                        Log.e(BenchmarkState.TAG, message, exception)
-                        destination = file
-                    }
+                Log.d(BenchmarkState.TAG, "Copying $file to $destination")
+                try {
+                    destination.mkdirs()
+                    file.copyTo(destination, overwrite = true)
+                } catch (exception: Throwable) {
+                    // This can happen when `additionalTestOutputDir` being passed in cannot
+                    // be written to. The shell does not have permissions to do the necessary
+                    // setup, and this can cause `adb pull` to fail.
+                    val message = """
+                        Unable to copy files to ${destination.absolutePath}.
+                        Please pull the Macrobenchmark results manually by using:
+                        adb pull ${file.absolutePath}
+                    """.trimIndent()
+                    Log.e(BenchmarkState.TAG, message, exception)
+                    destination = file
                 }
             }
             InstrumentationResults.reportAdditionalFileToCopy(
@@ -125,12 +126,10 @@
     }
 
     public fun relativePathFor(path: String): String {
-        var basePath = outputDirectory.absolutePath
-        val relativePath = if (path.indexOf(basePath) > 0) {
-            path.removePrefix("$basePath/")
-        } else {
-            basePath = dirUsableByAppAndShell.absolutePath
-            path.removePrefix("$basePath/")
+        val hasOutputDirectoryPrefix = path.startsWith(outputDirectory.absolutePath)
+        val relativePath = when {
+            hasOutputDirectoryPrefix -> path.removePrefix("${outputDirectory.absolutePath}/")
+            else -> path.removePrefix("${dirUsableByAppAndShell.absolutePath}/")
         }
         check(relativePath != path) {
             "$relativePath == $path"
diff --git a/benchmark/integration-tests/macrobenchmark-target/lint-baseline.xml b/benchmark/integration-tests/macrobenchmark-target/lint-baseline.xml
deleted file mode 100644
index 8f1aa4b..0000000
--- a/benchmark/integration-tests/macrobenchmark-target/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-</issues>
diff --git a/benchmark/integration-tests/macrobenchmark/lint-baseline.xml b/benchmark/integration-tests/macrobenchmark/lint-baseline.xml
deleted file mode 100644
index 8f1aa4b..0000000
--- a/benchmark/integration-tests/macrobenchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-</issues>
diff --git a/benchmark/junit4/lint-baseline.xml b/benchmark/junit4/lint-baseline.xml
deleted file mode 100644
index 8f1aa4b..0000000
--- a/benchmark/junit4/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-</issues>
diff --git a/benchmark/macro-junit4/lint-baseline.xml b/benchmark/macro-junit4/lint-baseline.xml
deleted file mode 100644
index 8f1aa4b..0000000
--- a/benchmark/macro-junit4/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-</issues>
diff --git a/benchmark/macro/lint-baseline.xml b/benchmark/macro/lint-baseline.xml
deleted file mode 100644
index 8f1aa4b..0000000
--- a/benchmark/macro/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-</issues>
diff --git a/biometric/biometric/src/main/res/values-ca/strings.xml b/biometric/biometric/src/main/res/values-ca/strings.xml
index bc8a6ab..e41ab0bb 100644
--- a/biometric/biometric/src/main/res/values-ca/strings.xml
+++ b/biometric/biometric/src/main/res/values-ca/strings.xml
@@ -19,7 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="fingerprint_dialog_touch_sensor" msgid="1072308044213194243">"Toca sensor d\'empremtes"</string>
     <string name="fingerprint_not_recognized" msgid="3873359464293253009">"No s\'ha reconegut"</string>
-    <string name="fingerprint_error_hw_not_available" msgid="8216738333501875566">"El maquinari per a empremtes dactilars no està disponible."</string>
+    <string name="fingerprint_error_hw_not_available" msgid="8216738333501875566">"El maquinari d\'empremtes digitals no està disponible."</string>
     <string name="fingerprint_error_no_fingerprints" msgid="7520712796891883488">"No s\'ha registrat cap empremta digital."</string>
     <string name="fingerprint_error_hw_not_present" msgid="6306988885793029438">"Aquest dispositiu no té sensor d\'empremtes dactilars"</string>
     <string name="fingerprint_error_user_canceled" msgid="7627716295344353987">"L\'usuari ha cancel·lat l\'operació d\'empremta digital."</string>
diff --git a/biometric/biometric/src/main/res/values-es-rUS/strings.xml b/biometric/biometric/src/main/res/values-es-rUS/strings.xml
index 4071fc9..eea350b 100644
--- a/biometric/biometric/src/main/res/values-es-rUS/strings.xml
+++ b/biometric/biometric/src/main/res/values-es-rUS/strings.xml
@@ -17,17 +17,17 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="fingerprint_dialog_touch_sensor" msgid="1072308044213194243">"Toca el sensor de huellas dig."</string>
+    <string name="fingerprint_dialog_touch_sensor" msgid="1072308044213194243">"Toca el sensor de huellas dac."</string>
     <string name="fingerprint_not_recognized" msgid="3873359464293253009">"No se reconoció"</string>
-    <string name="fingerprint_error_hw_not_available" msgid="8216738333501875566">"El hardware para detectar huellas digitales no está disponible."</string>
+    <string name="fingerprint_error_hw_not_available" msgid="8216738333501875566">"El hardware para detectar huellas dactilares no está disponible."</string>
     <string name="fingerprint_error_no_fingerprints" msgid="7520712796891883488">"No se registraron huellas digitales."</string>
-    <string name="fingerprint_error_hw_not_present" msgid="6306988885793029438">"Este dispositivo no tiene sensor de huellas digitales"</string>
-    <string name="fingerprint_error_user_canceled" msgid="7627716295344353987">"El usuario canceló la operación de huella digital."</string>
+    <string name="fingerprint_error_hw_not_present" msgid="6306988885793029438">"Este dispositivo no tiene sensor de huellas dactilares"</string>
+    <string name="fingerprint_error_user_canceled" msgid="7627716295344353987">"El usuario canceló la operación de huella dactilar."</string>
     <string name="fingerprint_error_lockout" msgid="7291787166416782245">"Demasiados intentos. Vuelve a intentarlo más tarde."</string>
     <string name="default_error_msg" msgid="4776854077120974966">"Error desconocido"</string>
     <string name="generic_error_user_canceled" msgid="7309881387583143581">"El usuario canceló la autenticación."</string>
     <string name="confirm_device_credential_password" msgid="5912733858573823945">"Usar contraseña"</string>
     <string name="generic_error_no_device_credential" msgid="3791785319221634505">"No se estableció ningún PIN, patrón ni contraseña."</string>
     <string name="generic_error_no_keyguard" msgid="1807436368654974044">"Este dispositivo no admite PIN, patrón ni contraseña."</string>
-    <string name="fingerprint_dialog_icon_description" msgid="5462024216548165325">"Ícono de huella digital"</string>
+    <string name="fingerprint_dialog_icon_description" msgid="5462024216548165325">"Ícono de huella dactilar"</string>
 </resources>
diff --git a/browser/browser/src/main/res/values-vi/strings.xml b/browser/browser/src/main/res/values-vi/strings.xml
index 956712c..983d194 100644
--- a/browser/browser/src/main/res/values-vi/strings.xml
+++ b/browser/browser/src/main/res/values-vi/strings.xml
@@ -19,5 +19,5 @@
     <string name="fallback_menu_item_open_in_browser" msgid="3413186855122069269">"Mở trong trình duyệt"</string>
     <string name="fallback_menu_item_copy_link" msgid="4566929209979330987">"Sao chép đường liên kết"</string>
     <string name="fallback_menu_item_share_link" msgid="7145444925855055364">"Chia sẻ liên kết"</string>
-    <string name="copy_toast_msg" msgid="3260749812566568062">"Đã sao chép đường liên kết vào khay nhớ tạm"</string>
+    <string name="copy_toast_msg" msgid="3260749812566568062">"Đã sao chép đường liên kết vào bảng nhớ tạm"</string>
 </resources>
diff --git a/buildSrc-tests/project-subsets/src/test/kotlin/androidx/build/ProjectSubsetsTest.kt b/buildSrc-tests/project-subsets/src/test/kotlin/androidx/build/ProjectSubsetsTest.kt
index 02cb205..0f02efd 100644
--- a/buildSrc-tests/project-subsets/src/test/kotlin/androidx/build/ProjectSubsetsTest.kt
+++ b/buildSrc-tests/project-subsets/src/test/kotlin/androidx/build/ProjectSubsetsTest.kt
@@ -56,9 +56,15 @@
      * Validates a specific project subset
      */
     fun validateSubset(name: String) {
+        val projectDir = File("../..").normalize()
+        var outDir = System.getenv("OUT_DIR")
+        if (outDir == null || outDir == "") {
+            outDir = File(projectDir, "../../out").normalize().toString()
+        }
         GradleRunner.create()
-            .withProjectDir(File("../..").normalize())
+            .withProjectDir(projectDir)
             .withArguments("-Pandroidx.projects=$name", "tasks")
+            .withTestKitDir(File(outDir, ".gradle-testkit"))
             .build(); // fails the test if the build fails
     }
 }
diff --git a/buildSrc-tests/src/test/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilderTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilderTest.kt
index 4798cfc..ff5b837 100644
--- a/buildSrc-tests/src/test/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilderTest.kt
+++ b/buildSrc-tests/src/test/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilderTest.kt
@@ -202,6 +202,7 @@
     <option name="wifi:disable" value="true" />
     <include name="google/unbundled/common/setup" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+    <option name="cleanup-apks" value="true" />
     <option name="test-file-name" value="clientPlaceholder.apk" />
     <option name="test-file-name" value="servicePlaceholder.apk" />
     </target_preparer>
diff --git a/buildSrc/build_dependencies.gradle b/buildSrc/build_dependencies.gradle
index 00cd453..5065107 100644
--- a/buildSrc/build_dependencies.gradle
+++ b/buildSrc/build_dependencies.gradle
@@ -24,7 +24,7 @@
 
 build_versions.kotlin = "1.4.31"
 build_versions.kotlin_coroutines = "1.4.1"
-build_versions.ksp = "1.4.30-1.0.0-alpha04"
+build_versions.ksp = "1.4.30-1.0.0-alpha05"
 
 build_versions.hilt = "2.33-beta"
 
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXExtension.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXExtension.kt
index 38e09c8..b8a6a40 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXExtension.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXExtension.kt
@@ -126,7 +126,7 @@
                 // add per-project overrides here
                 // for example
                 // the following project is intended to be accessed from Java
-                // ":compose:internal-lint-checks" -> return true
+                // ":compose:lint:internal-lint-checks" -> return true
                 // the following project is not intended to be accessed from Java
                 // ":annotation:annotation" -> return false
             }
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlaygroundRootPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlaygroundRootPlugin.kt
index aa96978..3e48e7c 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlaygroundRootPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlaygroundRootPlugin.kt
@@ -19,8 +19,7 @@
 import androidx.build.AndroidXRootPlugin.Companion.PREBUILT_OR_SNAPSHOT_EXT_NAME
 import androidx.build.AndroidXRootPlugin.Companion.PROJECT_OR_ARTIFACT_EXT_NAME
 import androidx.build.gradle.isRoot
-import groovy.util.XmlParser
-import groovy.xml.QName
+import groovy.xml.DOMBuilder
 import org.gradle.api.GradleException
 import org.gradle.api.Plugin
 import org.gradle.api.Project
@@ -39,10 +38,12 @@
 @Suppress("unused") // used in Playground Projects
 class AndroidXPlaygroundRootPlugin : Plugin<Project> {
     private lateinit var rootProject: Project
+
     /**
      * List of snapshot repositories to fetch AndroidX artifacts
      */
     private lateinit var repos: PlaygroundRepositories
+
     /**
      * The configuration for the plugin read from the gradle properties
      */
@@ -84,7 +85,6 @@
         project.extra.set(PREBUILT_OR_SNAPSHOT_EXT_NAME, prebuiltOrSnapshotClosure)
         project.configurations.all { configuration ->
             configuration.resolutionStrategy.dependencySubstitution.all { substitution ->
-                substitution.allowAndroidxSnapshotReplacement()
                 substitution.replaceIfSnapshot()
             }
         }
@@ -142,15 +142,6 @@
         return "$group:$artifact:$SNAPSHOT_MARKER"
     }
 
-    private fun DependencySubstitution.allowAndroidxSnapshotReplacement() {
-        val requested = this.requested
-        if (requested is ModuleComponentSelector && requested.group.startsWith("androidx") &&
-            requested.version.matches(Regex("^[0-9]+\\.[0-9]+\\.[0-9]+$"))
-        ) {
-            useTarget("${requested.group}:${requested.module}:${requested.version}+")
-        }
-    }
-
     private fun DependencySubstitution.replaceIfSnapshot() {
         val requested = this.requested
         if (requested is ModuleComponentSelector && requested.version == SNAPSHOT_MARKER) {
@@ -178,10 +169,15 @@
         } else {
             val metadataUrl = "${repos.snapshots}/$groupPath/$modulePath/maven-metadata.xml"
             URL(metadataUrl).openStream().use {
-                val parsedMetadata = XmlParser().parse(it)
-                val snapshotVersion = parsedMetadata
-                    .getAt(QName.valueOf("versioning"))
-                    .getAt("latest").text()
+                val parsedMetadata = DOMBuilder.parse(it.reader())
+                val versionNodes = parsedMetadata.getElementsByTagName("latest")
+                if (versionNodes.length != 1) {
+                    throw GradleException(
+                        "AndroidXPlaygroundRootPlugin#findSnapshotVersion expected exactly one " +
+                            "latest version in $metadataUrl, but got ${versionNodes.length}"
+                    )
+                }
+                val snapshotVersion = versionNodes.item(0).textContent
                 metadataCacheFile.parentFile.mkdirs()
                 metadataCacheFile.writeText(snapshotVersion, Charsets.UTF_8)
                 snapshotVersion
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
index 24cdd5a..4487044 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
@@ -44,6 +44,7 @@
 import com.android.build.gradle.LibraryPlugin
 import com.android.build.gradle.TestedExtension
 import com.android.build.gradle.api.ApkVariant
+import org.gradle.api.GradleException
 import org.gradle.api.JavaVersion.VERSION_1_8
 import org.gradle.api.Plugin
 import org.gradle.api.Project
@@ -675,6 +676,9 @@
     }
 }
 
+private const val PROJECTS_MAP_KEY = "projects"
+private const val ACCESSED_PROJECTS_MAP_KEY = "accessedProjectsMap"
+
 /**
  * Hides a project's Javadoc tasks from the output of `./gradlew tasks` by setting their group to
  * `null`.
@@ -698,8 +702,15 @@
             if (group != null) {
                 val module = "$group:$name"
 
+                if (project.rootProject.extra.has(ACCESSED_PROJECTS_MAP_KEY)) {
+                    throw GradleException(
+                        "Attempted to add $project to project map after " +
+                            "the contents of the map were accessed"
+                    )
+                }
                 @Suppress("UNCHECKED_CAST")
-                val projectModules = getProjectsMap()
+                val projectModules = project.rootProject.extra.get(PROJECTS_MAP_KEY)
+                    as ConcurrentHashMap<String, String>
                 projectModules[module] = path
             }
         }
@@ -723,7 +734,8 @@
 
 @Suppress("UNCHECKED_CAST")
 fun Project.getProjectsMap(): ConcurrentHashMap<String, String> {
-    return rootProject.extra.get("projects") as ConcurrentHashMap<String, String>
+    project.rootProject.extra.set(ACCESSED_PROJECTS_MAP_KEY, true)
+    return rootProject.extra.get(PROJECTS_MAP_KEY) as ConcurrentHashMap<String, String>
 }
 
 /**
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
index 1d82fb3..ce0b8d1 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
@@ -29,6 +29,7 @@
 import com.android.build.gradle.internal.tasks.factory.dependsOn
 import org.gradle.api.Plugin
 import org.gradle.api.Project
+import org.gradle.api.artifacts.component.ModuleComponentSelector
 import org.gradle.api.plugins.ExtraPropertiesExtension
 import org.gradle.api.plugins.JavaPlugin
 import org.gradle.api.tasks.bundling.Zip
@@ -80,8 +81,7 @@
         if (partiallyDejetifyArchiveTask != null)
             buildOnServerTask.dependsOn(partiallyDejetifyArchiveTask)
 
-        val projectModules = ConcurrentHashMap<String, String>()
-        extra.set("projects", projectModules)
+        extra.set("projects", ConcurrentHashMap<String, String>())
         buildOnServerTask.dependsOn(tasks.named(CheckExternalDependencyLicensesTask.TASK_NAME))
         // Anchor task that invokes running all subprojects :validateProperties tasks which ensure that
         // Android Studio sync is able to succeed.
@@ -178,7 +178,11 @@
         if (project.usingMaxDepVersions()) {
             // This requires evaluating all sub-projects to create the module:project map
             // and project dependencies.
-            evaluationDependsOnChildren()
+            allprojects { project2 ->
+                // evaluationDependsOnChildren isn't transitive so we must call it on each project
+                project2.evaluationDependsOnChildren()
+            }
+            val projectModules = getProjectsMap()
             subprojects { subproject ->
                 // TODO(153485458) remove most of these exceptions
                 if (!subproject.name.contains("hilt") &&
@@ -197,8 +201,14 @@
                 ) {
                     subproject.configurations.all { configuration ->
                         configuration.resolutionStrategy.dependencySubstitution.apply {
-                            for (e in projectModules) {
-                                substitute(module(e.key)).with(project(e.value))
+                            all { dep ->
+                                val requested = dep.getRequested()
+                                if (requested is ModuleComponentSelector) {
+                                    val module = requested.group + ":" + requested.module
+                                    if (projectModules.containsKey(module)) {
+                                        dep.useTarget(project(projectModules[module]!!))
+                                    }
+                                }
                             }
                         }
                     }
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt
index 0c75234..c68995a 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt
@@ -136,21 +136,57 @@
         private fun Project.configureAndroidCommonOptions(testedExtension: TestedExtension) {
             testedExtension.defaultConfig.minSdkVersion(21)
 
-            testedExtension.lintOptions.apply {
-                // Too many Kotlin features require synthetic accessors - we want to rely on R8 to
-                // remove these accessors
-                disable("SyntheticAccessor")
-                // Composable naming is normally a warning, but we ignore (in AndroidX)
-                // warnings in Lint, so we make it an error here so it will fail the build.
-                // Note that this causes 'UnknownIssueId' lint warnings in the build log when
-                // Lint tries to apply this rule to modules that do not have this lint check.
-                // Unfortunately suppressing this doesn't seem to work, and disabling it causes
-                // it just to log `Lint: Unknown issue id "ComposableNaming"`, which will still
-                // cause the build log simplifier to fail.
-                error("ComposableNaming")
-                error("ComposableLambdaParameterNaming")
-                error("ComposableLambdaParameterPosition")
-                error("CompositionLocalNaming")
+            afterEvaluate { project ->
+                val isPublished = project.extensions.findByType(AndroidXExtension::class.java)
+                    ?.type == LibraryType.PUBLISHED_LIBRARY
+
+                testedExtension.lintOptions.apply {
+                    // Too many Kotlin features require synthetic accessors - we want to rely on R8 to
+                    // remove these accessors
+                    disable("SyntheticAccessor")
+                    // These lint checks are normally a warning (or lower), but we ignore (in AndroidX)
+                    // warnings in Lint, so we make it an error here so it will fail the build.
+                    // Note that this causes 'UnknownIssueId' lint warnings in the build log when
+                    // Lint tries to apply this rule to modules that do not have this lint check, so
+                    // we disable that check too
+                    disable("UnknownIssueId")
+                    error("ComposableNaming")
+                    error("ComposableLambdaParameterNaming")
+                    error("ComposableLambdaParameterPosition")
+                    error("CompositionLocalNaming")
+                    error("ComposableModifierFactory")
+                    error("ModifierFactoryReturnType")
+                    error("ModifierFactoryExtensionFunction")
+                    error("ModifierParameter")
+
+                    // Paths we want to enable ListIterator checks for - for higher level levels it
+                    // won't have a noticeable performance impact, and we don't want developers
+                    // reading high level library code to worry about this.
+                    val listIteratorPaths = listOf(
+                        "compose:foundation",
+                        "compose:runtime",
+                        "compose:ui",
+                        "text"
+                    )
+
+                    // Paths we want to disable ListIteratorChecks for - these are not runtime
+                    // libraries and so Iterator allocation is not relevant.
+                    val ignoreListIteratorFilter = listOf(
+                        "compose:ui:ui-test",
+                        "compose:ui:ui-tooling",
+                        "compose:ui:ui-inspection",
+                    )
+
+                    // Disable ListIterator if we are not in a matching path, or we are in an
+                    // unpublished project
+                    if (
+                        listIteratorPaths.none { path.contains(it) } ||
+                        ignoreListIteratorFilter.any { path.contains(it) } ||
+                        !isPublished
+                    ) {
+                        disable("ListIterator")
+                    }
+                }
             }
 
             // TODO(148540713): remove this exclusion when Lint can support using multiple lint jars
@@ -162,7 +198,7 @@
                 "lintChecks",
                 project.dependencies.project(
                     mapOf(
-                        "path" to ":compose:internal-lint-checks",
+                        "path" to ":compose:lint:internal-lint-checks",
                         "configuration" to "shadow"
                     )
                 )
diff --git a/buildSrc/src/main/kotlin/androidx/build/BundleInsideHelper.kt b/buildSrc/src/main/kotlin/androidx/build/BundleInsideHelper.kt
index 452597f..7d9d5d0 100644
--- a/buildSrc/src/main/kotlin/androidx/build/BundleInsideHelper.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/BundleInsideHelper.kt
@@ -117,6 +117,54 @@
         }
     }
 
+    /**
+     * Creates a configuration for users to use that will be used bundle these dependency
+     * jars inside of this lint check's jar. This is required because lintPublish does
+     * not currently support dependencies, so instead we need to bundle any dependencies with the
+     * lint jar manually. (b/182319899)
+     *
+     * ```
+     * dependencies {
+     *     if (rootProject.hasProperty("android.injected.invoked.from.ide")) {
+     *         compileOnly(LINT_API_LATEST)
+     *     } else {
+     *         compileOnly(LINT_API_MIN)
+     *     }
+     *     compileOnly(KOTLIN_STDLIB)
+     *     // Include this library inside the resulting lint jar
+     *     bundleInside(project(":foo-lint-utils"))
+     * }
+     * ```
+     * @receiver the project that should bundle jars specified by these configurations
+     */
+    @JvmStatic
+    fun Project.forInsideLintJar() {
+        val bundle = configurations.create("bundleInside")
+        val compileOnly = configurations.getByName("compileOnly")
+        val testImplementation = configurations.getByName("testImplementation")
+        // bundleInside dependencies should be included as compileOnly as well
+        compileOnly.setExtendsFrom(listOf(bundle))
+        testImplementation.setExtendsFrom(listOf(bundle))
+
+        tasks.named("jar").configure { jarTask ->
+            jarTask as Jar
+            jarTask.dependsOn(bundle)
+            jarTask.from({
+                bundle
+                    // The stdlib is already bundled with lint, so no need to include it manually
+                    // in the lint.jar if any dependencies here depend on it
+                    .filter { !it.name.contains("kotlin-stdlib") }
+                    .map { file ->
+                        if (file.isDirectory) {
+                            file
+                        } else {
+                            zipTree(file)
+                        }
+                    }
+            })
+        }
+    }
+
     private fun Project.configureRepackageTaskForType(
         type: String,
         from: String,
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index ca5eda1..7fedccc 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -20,11 +20,11 @@
  * The list of versions codes of all the libraries in this project.
  */
 object LibraryVersions {
-    val ACTIVITY = Version("1.3.0-alpha04")
+    val ACTIVITY = Version("1.3.0-alpha05")
     val ADS_IDENTIFIER = Version("1.0.0-alpha04")
     val ANNOTATION = Version("1.3.0-alpha01")
-    val ANNOTATION_EXPERIMENTAL = Version("1.1.0-rc01")
-    val APPCOMPAT = Version("1.3.0-beta02")
+    val ANNOTATION_EXPERIMENTAL = Version("1.2.0-alpha01")
+    val APPCOMPAT = Version("1.4.0-alpha01")
     val APPSEARCH = Version("1.0.0-alpha01")
     val ARCH_CORE = Version("2.2.0-alpha01")
     val ARCH_CORE_TESTING = ARCH_CORE
@@ -41,14 +41,14 @@
     val CAMERA_VIDEO = Version("1.0.0-alpha01")
     val CAMERA_VIEW = Version("1.0.0-alpha23")
     val CARDVIEW = Version("1.1.0-alpha01")
-    val CAR_APP = Version("1.0.0-beta02")
+    val CAR_APP = Version("1.0.0-rc01")
     // Pre-release before confirming to the same version as the rest of the CAR_APP library group.
     val CAR_APP_PRE_RELEASE = Version("1.0.0-alpha01")
     val COLLECTION = Version("1.2.0-alpha02")
     val CONTENTPAGER = Version("1.1.0-alpha01")
     val COMPOSE = Version(System.getenv("COMPOSE_CUSTOM_VERSION") ?: "1.0.0-beta02")
     val COORDINATORLAYOUT = Version("1.2.0-alpha01")
-    val CORE = Version("1.5.0-beta03")
+    val CORE = Version("1.6.0-alpha01")
     val CORE_ANIMATION = Version("1.0.0-alpha03")
     val CORE_ANIMATION_TESTING = Version("1.0.0-alpha03")
     val CORE_APPDIGEST = Version("1.0.0-alpha01")
@@ -90,7 +90,7 @@
     val MEDIAROUTER = Version("1.3.0-alpha01")
     val NAVIGATION = Version("2.4.0-alpha01")
     val NAVIGATION_COMPOSE = Version("1.0.0-alpha09")
-    val PAGING = Version("3.0.0-beta02")
+    val PAGING = Version("3.0.0-beta03")
     val PAGING_COMPOSE = Version("1.0.0-alpha08")
     val PALETTE = Version("1.1.0-alpha01")
     val PRINT = Version("1.1.0-beta01")
@@ -101,7 +101,7 @@
     val RECYCLERVIEW_SELECTION = Version("2.0.0-alpha01")
     val REMOTECALLBACK = Version("1.0.0-alpha02")
     val RESOURCEINSPECTION = Version("1.0.0-alpha01")
-    val ROOM = Version("2.3.0-beta03")
+    val ROOM = Version("2.4.0-alpha01")
     val SAVEDSTATE = Version("1.2.0-alpha01")
     val SECURITY = Version("1.1.0-alpha03")
     val SECURITY_APP_AUTHENTICATOR = Version("1.0.0-alpha01")
@@ -131,18 +131,18 @@
     val VIEWPAGER = Version("1.1.0-alpha01")
     val VIEWPAGER2 = Version("1.1.0-alpha02")
     val WEAR = Version("1.2.0-alpha07")
-    val WEAR_COMPLICATIONS = Version("1.0.0-alpha09")
+    val WEAR_COMPLICATIONS = Version("1.0.0-alpha10")
     val WEAR_INPUT = Version("1.1.0-alpha02")
-    val WEAR_ONGOING = Version("1.0.0-alpha03")
-    val WEAR_PHONE_INTERACTIONS = Version("1.0.0-alpha03")
-    val WEAR_REMOTE_INTERACTIONS = Version("1.0.0-alpha02")
-    val WEAR_TILES = Version("1.0.0-alpha01")
+    val WEAR_ONGOING = Version("1.0.0-alpha04")
+    val WEAR_PHONE_INTERACTIONS = Version("1.0.0-alpha04")
+    val WEAR_REMOTE_INTERACTIONS = Version("1.0.0-alpha03")
+    val WEAR_TILES = Version("1.0.0-alpha02")
     val WEAR_TILES_DATA = WEAR_TILES
-    val WEAR_WATCHFACE = Version("1.0.0-alpha09")
-    val WEAR_WATCHFACE_CLIENT = Version("1.0.0-alpha09")
-    val WEAR_WATCHFACE_DATA = Version("1.0.0-alpha09")
-    val WEAR_WATCHFACE_EDITOR = Version("1.0.0-alpha09")
-    val WEAR_WATCHFACE_STYLE = Version("1.0.0-alpha09")
+    val WEAR_WATCHFACE = Version("1.0.0-alpha10")
+    val WEAR_WATCHFACE_CLIENT = Version("1.0.0-alpha10")
+    val WEAR_WATCHFACE_DATA = Version("1.0.0-alpha10")
+    val WEAR_WATCHFACE_EDITOR = Version("1.0.0-alpha10")
+    val WEAR_WATCHFACE_STYLE = Version("1.0.0-alpha10")
     val WEBKIT = Version("1.5.0-alpha01")
     val WINDOW = Version("1.0.0-alpha05")
     val WINDOW_EXTENSIONS = Version("1.0.0-alpha01")
diff --git a/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt b/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt
index 49844b1..e58490b 100644
--- a/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt
@@ -32,7 +32,6 @@
 import org.gradle.kotlin.dsl.configure
 import org.gradle.kotlin.dsl.create
 import org.gradle.kotlin.dsl.findByType
-import org.gradle.kotlin.dsl.named
 import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
 import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
 import java.io.File
@@ -63,26 +62,25 @@
             publications {
                 if (appliesJavaGradlePluginPlugin()) {
                     // The 'java-gradle-plugin' will also add to the 'pluginMaven' publication
-                    it.create<MavenPublication>("pluginMaven").pom { pom ->
-                        addInformativeMetadata(pom, extension)
-                        tweakDependenciesMetadata(extension, pom)
-                    }
+                    it.create<MavenPublication>("pluginMaven")
                     tasks.getByName("publishPluginMavenPublicationToMavenRepository").doFirst {
                         removePreviouslyUploadedArchives(androidxGroup)
                     }
                 } else {
                     it.create<MavenPublication>("maven") {
                         from(component)
-                        pom { pom ->
-                            addInformativeMetadata(pom, extension)
-                            tweakDependenciesMetadata(extension, pom)
-                        }
                     }
                     tasks.getByName("publishMavenPublicationToMavenRepository").doFirst {
                         removePreviouslyUploadedArchives(androidxGroup)
                     }
                 }
             }
+            publications.withType(MavenPublication::class.java).all {
+                it.pom { pom ->
+                    addInformativeMetadata(extension, pom)
+                    tweakDependenciesMetadata(extension, pom)
+                }
+            }
         }
 
         // Register it as part of release so that we create a Zip file for it
@@ -103,42 +101,22 @@
         }
 
         if (isMultiplatformEnabled()) {
-            configureMultiplatformPublication(extension)
+            configureMultiplatformPublication()
         }
     }
 }
 
-private fun Project.configureMultiplatformPublication(
-    extension: AndroidXExtension
-) {
+private fun Project.configureMultiplatformPublication() {
     val multiplatformExtension = extensions.findByType<KotlinMultiplatformExtension>() ?: return
 
     // publishMavenPublicationToMavenRepository will produce conflicting artifacts with the same
     // name as the artifacts producing by publishKotlinMultiplatformPublicationToMavenRepository
     project.tasks.findByName("publishMavenPublicationToMavenRepository")?.enabled = false
 
-    configure<PublishingExtension> {
-        publications {
-            it.named<MavenPublication>("kotlinMultiplatform") {
-                pom { pom ->
-                    addInformativeMetadata(pom, extension)
-                    tweakDependenciesMetadata(extension, pom)
-                }
-            }
-        }
-    }
-
     multiplatformExtension.targets.all { target ->
         if (target is KotlinAndroidTarget) {
             target.publishAllLibraryVariants()
         }
-
-        target.mavenPublication { publication ->
-            publication.pom { pom ->
-                addInformativeMetadata(pom, extension)
-                tweakDependenciesMetadata(extension, pom)
-            }
-        }
     }
 }
 
@@ -169,7 +147,7 @@
     projectArchiveDir.deleteRecursively()
 }
 
-private fun Project.addInformativeMetadata(pom: MavenPom, extension: AndroidXExtension) {
+private fun Project.addInformativeMetadata(extension: AndroidXExtension, pom: MavenPom) {
     pom.name.set(provider { extension.name })
     pom.description.set(provider { extension.description })
     pom.url.set(
diff --git a/buildSrc/src/main/kotlin/androidx/build/Release.kt b/buildSrc/src/main/kotlin/androidx/build/Release.kt
index 6cac722..75e7a472 100644
--- a/buildSrc/src/main/kotlin/androidx/build/Release.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/Release.kt
@@ -23,6 +23,7 @@
 import org.gradle.api.tasks.Nested
 import org.gradle.api.tasks.TaskProvider
 import org.gradle.api.tasks.bundling.Zip
+import org.gradle.plugin.devel.GradlePluginDevelopmentExtension
 import java.io.File
 import java.util.TreeSet
 
@@ -181,7 +182,7 @@
         }
         val version = project.version
 
-        var zipTasks = listOf(
+        val zipTasks = listOf(
             getProjectZipTask(project),
             getGroupReleaseZipTask(project, mavenGroup),
             getGlobalFullZipTask(project)
@@ -193,9 +194,25 @@
         )
         val publishTask = project.tasks.named("publish")
         zipTasks.forEach {
-            it.configure {
-                it.candidates.add(artifact)
-                it.dependsOn(publishTask)
+            it.configure { zipTask ->
+                zipTask.candidates.add(artifact)
+
+                // Add additional artifacts needed for Gradle Plugins
+                if (extension.type == LibraryType.GRADLE_PLUGIN) {
+                    project.extensions.getByType(
+                        GradlePluginDevelopmentExtension::class.java
+                    ).plugins.forEach { plugin ->
+                        zipTask.candidates.add(
+                            Artifact(
+                                mavenGroup = plugin.id,
+                                projectName = "${plugin.id}.gradle.plugin",
+                                version = version.toString()
+                            )
+                        )
+                    }
+                }
+
+                zipTask.dependsOn(publishTask)
             }
         }
     }
@@ -280,26 +297,20 @@
     private fun getProjectZipTask(
         project: Project
     ): TaskProvider<GMavenZipTask> {
-        val taskName = "$PROJECT_ARCHIVE_ZIP_TASK_NAME"
-        val taskProvider: TaskProvider<GMavenZipTask> = project.maybeRegister(
-            name = taskName,
-            onConfigure = {
-                GMavenZipTask.ConfigAction(
-                    getParams(
-                        project = project,
-                        distDir = File(
-                            project.getDistributionDirectory(),
-                            PROJECT_ZIPS_FOLDER
-                        ),
-                        fileNamePrefix = project.projectZipPrefix()
-                    ).copy(
-                        includeMetadata = true
-                    )
-                ).execute(it)
-            },
-            onRegister = {
-            }
-        )
+        val taskProvider = project.tasks.register(
+            PROJECT_ARCHIVE_ZIP_TASK_NAME,
+            GMavenZipTask::class.java
+        ) {
+            GMavenZipTask.ConfigAction(
+                getParams(
+                    project = project,
+                    distDir = File(project.getDistributionDirectory(), PROJECT_ZIPS_FOLDER),
+                    fileNamePrefix = project.projectZipPrefix()
+                ).copy(
+                    includeMetadata = true
+                )
+            ).execute(it)
+        }
         project.addToBuildOnServer(taskProvider)
         return taskProvider
     }
diff --git a/buildSrc/src/main/kotlin/androidx/build/ReportLibraryMetricsTask.kt b/buildSrc/src/main/kotlin/androidx/build/ReportLibraryMetricsTask.kt
index 9edc2ba..bbbeffc 100644
--- a/buildSrc/src/main/kotlin/androidx/build/ReportLibraryMetricsTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/ReportLibraryMetricsTask.kt
@@ -31,12 +31,12 @@
 import org.json.simple.JSONObject
 import java.io.File
 
-private const val AAR_FILE_EXTENSION = ".aar"
 private const val BYTECODE_SIZE = "bytecode_size"
 private const val METHOD_COUNT = "method_count"
 private const val METRICS_DIRECTORY = "librarymetrics"
 private const val JSON_FILE_EXTENSION = ".json"
 private const val JAR_FILE_EXTENSION = ".jar"
+private const val LINT_JAR = "lint$JAR_FILE_EXTENSION"
 
 @CacheableTask
 abstract class ReportLibraryMetricsTask : DefaultTask() {
@@ -77,7 +77,10 @@
 
     private fun getJarFiles(): List<File> {
         return jarFiles.files.filter { file ->
-            file.name.endsWith(JAR_FILE_EXTENSION)
+            file.name.endsWith(JAR_FILE_EXTENSION) &&
+                // AARs bundle a `lint.jar` that contains lint checks published by the library -
+                // this isn't runtime code and is not part of the actual library, so ignore it.
+                file.name != LINT_JAR
         }
     }
 
diff --git a/buildSrc/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt b/buildSrc/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
index 44c28d7..a299124 100644
--- a/buildSrc/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
@@ -130,6 +130,12 @@
     if (name.startsWith("lint")) return false
     if (name == "metalava") return false
 
+    // Don't check any configurations that directly bundle the dependencies with the output
+    if (name == "bundleInside") return false
+
+    // Don't check any compile-only configurations
+    if (name.startsWith("compile")) return false
+
     return true
 }
 
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
index 2e36da9..1943535 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
@@ -236,6 +236,7 @@
     private val ignoreUnknownProjects: Boolean = false,
     private val projectSubset: ProjectSubset = ProjectSubset.ALL_AFFECTED_PROJECTS,
     private val cobuiltTestPaths: Set<Set<String>> = COBUILT_TEST_PATHS,
+    private val alwaysBuildIfExistsPaths: Set<String> = ALWAYS_BUILD_IF_EXISTS,
     private val injectedGitClient: GitClient? = null,
     private val baseCommitOverride: String? = null
 ) : AffectedModuleDetector(logger) {
@@ -274,7 +275,10 @@
     }
 
     private val alwaysBuild by lazy {
-        ALWAYS_BUILD.map { path -> rootProject.project(path) }
+        // For each path in alwaysBuildIfExistsPaths, if that path doesn't exist, then the developer
+        // must have disabled a project that they weren't interested in using during this run.
+        // Otherwise, we must always build the corresponding project during full builds.
+        alwaysBuildIfExistsPaths.map { path -> rootProject.findProject(path) }.filterNotNull()
     }
 
     /**
@@ -473,9 +477,13 @@
     }
 
     companion object {
-        // dummy test to ensure no failure due to "no instrumentation. We can eventually remove
-        // if we resolve b/127819369
-        private val ALWAYS_BUILD = setOf(":placeholder-tests")
+        // Project paths that we always build if they exist
+        private val ALWAYS_BUILD_IF_EXISTS = setOf(
+            // placeholder test project to ensure no failure due to no instrumentation.
+            // We can eventually remove if we resolve b/127819369
+            ":placeholder-tests",
+            ":buildSrc-tests:project-subsets"
+        )
 
         // Some tests are codependent even if their modules are not. Enable manual bundling of tests
         private val COBUILT_TEST_PATHS = setOf(
diff --git a/buildSrc/src/main/kotlin/androidx/build/doclava/OWNERS b/buildSrc/src/main/kotlin/androidx/build/doclava/OWNERS
new file mode 100644
index 0000000..45a5601
--- /dev/null
+++ b/buildSrc/src/main/kotlin/androidx/build/doclava/OWNERS
@@ -0,0 +1,2 @@
[email protected]
[email protected]
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt
index bf49a3c..2e01a77 100644
--- a/buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt
@@ -142,6 +142,7 @@
                             it.exclude("**/META-INF/**")
                             it.exclude("**/OWNERS")
                             it.exclude("**/package.html")
+                            it.exclude("**/*.md")
                         }
                     }
                 }
diff --git a/buildSrc/src/main/kotlin/androidx/build/docs/OWNERS b/buildSrc/src/main/kotlin/androidx/build/docs/OWNERS
new file mode 100644
index 0000000..45a5601
--- /dev/null
+++ b/buildSrc/src/main/kotlin/androidx/build/docs/OWNERS
@@ -0,0 +1,2 @@
[email protected]
[email protected]
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/androidx/build/dokka/OWNERS b/buildSrc/src/main/kotlin/androidx/build/dokka/OWNERS
new file mode 100644
index 0000000..45a5601
--- /dev/null
+++ b/buildSrc/src/main/kotlin/androidx/build/dokka/OWNERS
@@ -0,0 +1,2 @@
[email protected]
[email protected]
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt b/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
index cd2726e..6ef0465 100644
--- a/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/metalava/MetalavaTasks.kt
@@ -164,6 +164,7 @@
             task.inputApiLocation.set(generateApi.flatMap { it.apiLocation })
             task.outputApiLocations.set(checkApi.flatMap { it.checkedInApis })
             task.forceUpdate = project.hasProperty("force")
+            task.mavenVersion = extension.mavenVersion.toString()
             task.dependsOn(generateApi)
 
             // If a developer (accidentally) makes a non-backwards compatible change to an API,
diff --git a/buildSrc/src/main/kotlin/androidx/build/metalava/UpdateApiTask.kt b/buildSrc/src/main/kotlin/androidx/build/metalava/UpdateApiTask.kt
index 369ded7d..f10b44f 100644
--- a/buildSrc/src/main/kotlin/androidx/build/metalava/UpdateApiTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/metalava/UpdateApiTask.kt
@@ -16,6 +16,7 @@
 
 package androidx.build.metalava
 
+import androidx.build.Version
 import androidx.build.checkapi.ApiLocation
 import com.google.common.io.Files
 import org.gradle.api.DefaultTask
@@ -46,8 +47,11 @@
     @get:Input
     var forceUpdate: Boolean = false
 
+    @get:Input
+    var mavenVersion: String? = null
+
     @InputFiles
-    fun getTaskInputs(): List<File>? {
+    fun getTaskInputs(): List<File> {
         val inputApi = inputApiLocation.get()
         return listOf(
             inputApi.publicApiFile,
@@ -71,16 +75,12 @@
 
     @TaskAction
     fun exec() {
-        var permitOverwriting = true
-        for (outputApi in outputApiLocations.get()) {
-            val version = outputApi.version()
-            if (version != null && version.isFinalApi() &&
-                outputApi.publicApiFile.exists() &&
-                !forceUpdate
-            ) {
-                permitOverwriting = false
-            }
-        }
+        // If the library has finalized its API surface (e.g. beta or later) then only allow
+        // changing the API surface (e.g. overwrite versioned API files) if we're forcing the
+        // update.
+        val version = mavenVersion?.let { Version(it) }
+        val permitOverwriting = version == null || !version.isFinalApi() || forceUpdate
+
         for (outputApi in outputApiLocations.get()) {
             val inputApi = inputApiLocation.get()
             copy(
diff --git a/buildSrc/src/main/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilder.kt b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilder.kt
index fdee719..673b7a6 100644
--- a/buildSrc/src/main/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilder.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilder.kt
@@ -59,31 +59,29 @@
             .append(APK_INSTALL_OPTION.replace("APK_NAME", testApkName))
         if (!appApkName.isNullOrEmpty())
             sb.append(APK_INSTALL_OPTION.replace("APK_NAME", appApkName!!))
+        // Temporary hardcoded hack for b/181810492
+        else if (applicationId == "androidx.benchmark.macro.test") {
+            sb.append(
+                APK_INSTALL_OPTION.replace(
+                    "APK_NAME",
+                    /* ktlint-disable max-line-length */
+                    "benchmark-integration-tests-macrobenchmark-target_macrobenchmark-target-release.apk"
+                    /* ktlint-enable max-line-length */
+                )
+            )
+        }
         sb.append(TARGET_PREPARER_CLOSE)
             .append(TEST_BLOCK_OPEN)
             .append(RUNNER_OPTION.replace("TEST_RUNNER", testRunner))
             .append(PACKAGE_OPTION.replace("APPLICATION_ID", applicationId))
-        if (runAllTests) {
-            if (!isPostsubmit) {
-                sb.append(FLAKY_TEST_OPTION)
-            }
-            sb.append(TEST_BLOCK_CLOSE)
-        } else {
-            if (!isPostsubmit) {
-                sb.append(FLAKY_TEST_OPTION)
-            }
-            sb.append(SMALL_TEST_OPTIONS)
-                .append(TEST_BLOCK_CLOSE)
-                .append(TEST_BLOCK_OPEN)
-            if (!isPostsubmit) {
-                sb.append(FLAKY_TEST_OPTION)
-            }
-            sb.append(RUNNER_OPTION.replace("TEST_RUNNER", testRunner))
-                .append(PACKAGE_OPTION.replace("APPLICATION_ID", applicationId))
-                .append(MEDIUM_TEST_OPTIONS)
-                .append(TEST_BLOCK_CLOSE)
+        if (!isPostsubmit) {
+            sb.append(FLAKY_TEST_OPTION)
         }
-        sb.append(CONFIGURATION_CLOSE)
+        if (!runAllTests) {
+            sb.append(SMALL_AND_MEDIUM_TEST_OPTIONS)
+        }
+        sb.append(TEST_BLOCK_CLOSE)
+            .append(CONFIGURATION_CLOSE)
         return sb.toString()
     }
 }
@@ -176,31 +174,13 @@
             if (!isPostsubmit) {
                 sb.append(FLAKY_TEST_OPTION)
             }
-            sb.append(SMALL_TEST_OPTIONS)
+            sb.append(SMALL_AND_MEDIUM_TEST_OPTIONS)
                 .append(TEST_BLOCK_CLOSE)
                 .append(TEST_BLOCK_OPEN)
                 .append(RUNNER_OPTION.replace("TEST_RUNNER", testRunner))
-                .append(PACKAGE_OPTION.replace("APPLICATION_ID", clientApplicationId))
-                .append(mediaInstrumentationArgs())
-                .append(MEDIUM_TEST_OPTIONS)
-            if (!isPostsubmit) {
-                sb.append(FLAKY_TEST_OPTION)
-            }
-            sb.append(TEST_BLOCK_CLOSE)
-                .append(TEST_BLOCK_OPEN)
-                .append(RUNNER_OPTION.replace("TEST_RUNNER", testRunner))
                 .append(PACKAGE_OPTION.replace("APPLICATION_ID", serviceApplicationId))
                 .append(mediaInstrumentationArgs())
-                .append(SMALL_TEST_OPTIONS)
-            if (!isPostsubmit) {
-                sb.append(FLAKY_TEST_OPTION)
-            }
-            sb.append(TEST_BLOCK_CLOSE)
-                .append(TEST_BLOCK_OPEN)
-                .append(RUNNER_OPTION.replace("TEST_RUNNER", testRunner))
-                .append(PACKAGE_OPTION.replace("APPLICATION_ID", serviceApplicationId))
-                .append(mediaInstrumentationArgs())
-                .append(MEDIUM_TEST_OPTIONS)
+                .append(SMALL_AND_MEDIUM_TEST_OPTIONS)
             if (!isPostsubmit) {
                 sb.append(FLAKY_TEST_OPTION)
             }
@@ -270,6 +250,10 @@
 
 """.trimIndent()
 
+/**
+ * We can't remove the apk on API < 27 due to a platform crash that occurs
+ * when handling a PACKAGE_CHANGED broadcast after the package has been removed. b/37264334
+ */
 private val TARGET_PREPARER_OPEN = """
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
     <option name="cleanup-apks" value="false" />
@@ -278,6 +262,7 @@
 
 private val MEDIA_TARGET_PREPARER_OPEN = """
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+    <option name="cleanup-apks" value="true" />
 
 """.trimIndent()
 
@@ -327,14 +312,9 @@
 
 """.trimIndent()
 
-private val SMALL_TEST_OPTIONS = """
+private val SMALL_AND_MEDIUM_TEST_OPTIONS = """
     <option name="size" value="small" />
-
-""".trimIndent()
-
-private val MEDIUM_TEST_OPTIONS = """
     <option name="size" value="medium" />
-
 """.trimIndent()
 
 private val CLIENT_PREVIOUS = """
diff --git a/buildSrc/studio_versions.properties b/buildSrc/studio_versions.properties
index cba12ba..a305f32 100644
--- a/buildSrc/studio_versions.properties
+++ b/buildSrc/studio_versions.properties
@@ -3,11 +3,11 @@
 # when updating AGP versions
 
 # the version of the Android Gradle Plugin
-agp=4.2.0-beta04
+agp=4.2.0-beta06
 # Note, lint version must be kept in sync with agp
 # NOTE: When updating the lint version we also need to update the `api` version supported
 #  by `IssueRegistry`'s.' For e.g. aosp/1331903
-lint=27.2.0-beta04
+lint=27.2.0-beta06
 
 # Version properties for Android Studio which should correspond to the version of AGP
 #
@@ -19,6 +19,6 @@
 # The download url should contain: ...ide-zips/3.6.0.5/android-studio-ide-191.5721125-linux...
 # From this, the first number (3.6.0.5) is [studio_version], the first number in the filename (192)
 # is the [idea_major_version] and the last number (5721125) is the [studio_build_number].
-studio_version=4.2.0.20
+studio_version=4.2.0.22
 idea_major_version=202
-studio_build_number=7094744
+studio_build_number=7188722
diff --git a/camera/camera-camera2-pipe-testing/build.gradle b/camera/camera-camera2-pipe-testing/build.gradle
index 4e420b00..c340844 100644
--- a/camera/camera-camera2-pipe-testing/build.gradle
+++ b/camera/camera-camera2-pipe-testing/build.gradle
@@ -50,10 +50,6 @@
         minSdkVersion 21
     }
 
-    sourceSets {
-        test.java.srcDirs += 'src/robolectric/java'
-    }
-
     // Use Robolectric 4.+
     testOptions.unitTests.includeAndroidResources = true
 }
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulator.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulator.kt
index 0ab26e4..8eb6fb9 100644
--- a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulator.kt
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulator.kt
@@ -40,12 +40,12 @@
         check(config.camera == metadata.camera)
     }
 
-    private val requestProcessor = FakeRequestProcessor()
+    private val fakeRequestProcessor = FakeRequestProcessor()
     private val cameraPipe = CameraPipe.External()
     public val cameraGraph = cameraPipe.create(
         config,
         FakeCameraDevices(listOf(metadata)),
-        requestProcessor
+        fakeRequestProcessor
     )
 
     private var frameClockNanos = atomic(0L)
@@ -64,7 +64,7 @@
         // available it will suspend until the next interaction with the request processor.
         if (pendingFrameQueue.isEmpty()) {
             val requestSequence =
-                withTimeoutOrNull(timeMillis = 50) { requestProcessor.nextRequestSequence() }
+                withTimeoutOrNull(timeMillis = 200) { fakeRequestProcessor.nextRequestSequence() }
                     ?: return null
 
             // Each sequence is processed as a group, and if a sequence contains multiple requests
diff --git a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeRequestProcessor.kt b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeRequestProcessor.kt
index fccdfd88..8b36e26 100644
--- a/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeRequestProcessor.kt
+++ b/camera/camera-camera2-pipe-testing/src/main/java/androidx/camera/camera2/pipe/testing/FakeRequestProcessor.kt
@@ -355,3 +355,27 @@
         val startRepeating: Boolean = false
     )
 }
+
+suspend fun FakeRequestProcessor.awaitEvent(
+    request: Request? = null,
+    filter: (event: FakeRequestProcessor.Event) -> Boolean
+): FakeRequestProcessor.Event {
+
+    var event: FakeRequestProcessor.Event
+    var loopCount = 0
+    while (loopCount < 10) {
+        loopCount++
+        event = this.nextEvent()
+
+        if (request != null) {
+            val contains = event.requestSequence?.requests?.contains(request) ?: false
+            if (filter(event) && contains) {
+                return event
+            }
+        } else if (filter(event)) {
+            return event
+        }
+    }
+
+    throw IllegalStateException("Failed to observe a submit event containing $request")
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-testing/src/robolectric/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt b/camera/camera-camera2-pipe-testing/src/robolectric/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt
deleted file mode 100644
index ddf221c..0000000
--- a/camera/camera-camera2-pipe-testing/src/robolectric/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.camera2.pipe.testing
-
-import org.junit.runners.model.FrameworkMethod
-import org.robolectric.RobolectricTestRunner
-import org.robolectric.internal.bytecode.InstrumentationConfiguration
-
-/**
- * A [RobolectricTestRunner] for [androidx.camera.camera2.pipe] unit tests.
- *
- * This test runner disables instrumentation for the [androidx.camera.camera2.pipe] and
- * [androidx.camera.camera2.pipe.testing] packages.
- *
- * Robolectric tries to instrument Kotlin classes, and it throws errors when it encounters
- * companion objects, constructors with default values for parameters, and data classes with
- * inline classes. We don't need shadowing of our classes because we want to use the actual
- * objects in our tests.
- */
-public class RobolectricCameraPipeTestRunner(testClass: Class<*>) :
-    RobolectricTestRunner(testClass) {
-    override fun createClassLoaderConfig(method: FrameworkMethod?): InstrumentationConfiguration {
-        val builder = InstrumentationConfiguration.Builder(super.createClassLoaderConfig(method))
-        builder.doNotInstrumentPackage("androidx.camera.camera2.pipe")
-        builder.doNotInstrumentPackage("androidx.camera.camera2.pipe.testing")
-        return builder.build()
-    }
-}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulatorTest.kt b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulatorTest.kt
index 4bb4026..f323d03 100644
--- a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulatorTest.kt
+++ b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/CameraGraphSimulatorTest.kt
@@ -36,7 +36,6 @@
 import kotlinx.coroutines.withContext
 import kotlinx.coroutines.withTimeout
 import kotlinx.coroutines.withTimeoutOrNull
-import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.annotation.Config
@@ -62,7 +61,6 @@
     private val stream = simulator.cameraGraph.streams[streamConfig]!!
 
     @Test
-    @Ignore // TODO(b/179825103): Ensure test does not flake
     fun simulatorCanSimulateRepeatingFrames() = runBlocking {
         val listener = FakeRequestListener()
         val request = Request(
@@ -74,7 +72,9 @@
         }
         simulator.cameraGraph.start()
 
-        val frame = simulator.simulateNextFrame()!!
+        val frame = simulator.simulateNextFrame()
+        assertThat(frame).isNotNull()
+        frame!! // Tell kotlin that this is not null.
 
         assertThat(frame.request).isSameInstanceAs(request)
         assertThat(frame.frameNumber.value).isGreaterThan(0)
@@ -195,7 +195,6 @@
         assertThat(lossEvent.streamId).isEqualTo(stream.id)
     }
 
-    @Ignore // TODO(b/179825103): Ensure test does not flake
     @Test
     fun simulatorCanIssueMultipleFrames() = runBlocking {
         val listener = FakeRequestListener()
@@ -235,7 +234,7 @@
             frame3.simulateComplete(resultMetadata)
         }
 
-        val startEvents = withTimeout(timeMillis = 50) {
+        val startEvents = withTimeout(timeMillis = 150) {
             listener.onStartedFlow.take(3).toList()
         }
         assertThat(startEvents).hasSize(3)
diff --git a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunnerTest.kt b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt
similarity index 64%
rename from camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunnerTest.kt
rename to camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt
index b5f7555..13a41fc 100644
--- a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunnerTest.kt
+++ b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 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.
@@ -21,7 +21,31 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import org.junit.runners.model.FrameworkMethod
+import org.robolectric.RobolectricTestRunner
 import org.robolectric.annotation.Config
+import org.robolectric.internal.bytecode.InstrumentationConfiguration
+
+/**
+ * A [RobolectricTestRunner] for [androidx.camera.camera2.pipe] unit tests.
+ *
+ * This test runner disables instrumentation for the [androidx.camera.camera2.pipe] and
+ * [androidx.camera.camera2.pipe.testing] packages.
+ *
+ * Robolectric tries to instrument Kotlin classes, and it throws errors when it encounters
+ * companion objects, constructors with default values for parameters, and data classes with
+ * inline classes. We don't need shadowing of our classes because we want to use the actual
+ * objects in our tests.
+ */
+public class RobolectricCameraPipeTestRunner(testClass: Class<*>) :
+    RobolectricTestRunner(testClass) {
+    override fun createClassLoaderConfig(method: FrameworkMethod?): InstrumentationConfiguration {
+        val builder = InstrumentationConfiguration.Builder(super.createClassLoaderConfig(method))
+        builder.doNotInstrumentPackage("androidx.camera.camera2.pipe")
+        builder.doNotInstrumentPackage("androidx.camera.camera2.pipe.testing")
+        return builder.build()
+    }
+}
 
 @Suppress("EXPERIMENTAL_FEATURE_WARNING")
 public inline class TestValue(public val value: String)
diff --git a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCamerasTest.kt b/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCamerasTest.kt
deleted file mode 100644
index 87f16db..0000000
--- a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCamerasTest.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.camera.camera2.pipe.testing
-
-import android.content.Context
-import android.hardware.camera2.CameraCharacteristics
-import android.os.Build
-import android.os.Looper
-import androidx.test.core.app.ApplicationProvider
-import com.google.common.truth.Truth.assertThat
-import org.junit.After
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.Shadows.shadowOf
-import org.robolectric.annotation.Config
-
-@RunWith(RobolectricCameraPipeTestRunner::class)
-@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-class RobolectricCamerasTest {
-    private val context = ApplicationProvider.getApplicationContext() as Context
-    private val mainLooper = shadowOf(Looper.getMainLooper())
-
-    @Test
-    fun fakeCamerasCanBeOpened() {
-        val fakeCameraId = RobolectricCameras.create(
-            mapOf(CameraCharacteristics.LENS_FACING to CameraCharacteristics.LENS_FACING_BACK)
-        )
-        val fakeCamera = RobolectricCameras.open(fakeCameraId)
-
-        assertThat(fakeCamera).isNotNull()
-        assertThat(fakeCamera.cameraId).isEqualTo(fakeCameraId)
-        assertThat(fakeCamera.cameraDevice).isNotNull()
-        assertThat(fakeCamera.characteristics).isNotNull()
-        assertThat(fakeCamera.characteristics[CameraCharacteristics.LENS_FACING]).isNotNull()
-        assertThat(fakeCamera.metadata).isNotNull()
-        assertThat(fakeCamera.metadata[CameraCharacteristics.LENS_FACING]).isNotNull()
-    }
-
-    @After
-    fun teardown() {
-        mainLooper.idle()
-        RobolectricCameras.clear()
-    }
-}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe/build.gradle b/camera/camera-camera2-pipe/build.gradle
index 014c031..50d5b87 100644
--- a/camera/camera-camera2-pipe/build.gradle
+++ b/camera/camera-camera2-pipe/build.gradle
@@ -61,11 +61,6 @@
         minSdkVersion 21
     }
 
-    // Include additional robolectric utilities in tests
-    sourceSets {
-        test.java.srcDirs += ['../camera-camera2-pipe-testing/src/robolectric/java']
-    }
-
     // Use Robolectric 4.+
     testOptions.unitTests.includeAndroidResources = true
 }
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/CameraPipeTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/CameraPipeTest.kt
index 51dfe49..1f4383c 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/CameraPipeTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/CameraPipeTest.kt
@@ -18,15 +18,15 @@
 
 import android.content.Context
 import android.os.Build
-import androidx.camera.camera2.pipe.testing.RobolectricCameraPipeTestRunner
 import androidx.camera.camera2.pipe.testing.FakeCameraDevices
 import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
-import androidx.camera.camera2.pipe.testing.RobolectricCameras
 import androidx.camera.camera2.pipe.testing.FakeRequestProcessor
+import androidx.camera.camera2.pipe.testing.RobolectricCameraPipeTestRunner
+import androidx.camera.camera2.pipe.testing.RobolectricCameras
+import androidx.camera.camera2.pipe.testing.awaitEvent
 import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.runBlocking
-import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.annotation.Config
@@ -73,7 +73,6 @@
     }
 
     @Test
-    @Ignore("b/180539013: Test is currently flaky")
     fun createExternalCameraGraph() {
         val fakeRequestProcessor = FakeRequestProcessor()
         val fakeCameraMetadata = FakeCameraMetadata()
@@ -103,7 +102,7 @@
 
             cameraGraph.stop()
 
-            val closeEvent = fakeRequestProcessor.nextEvent()
+            val closeEvent = fakeRequestProcessor.awaitEvent { it.close }
             assertThat(closeEvent.close).isTrue()
         }
 
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphProcessorTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphProcessorTest.kt
index a191e48..10a8811 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphProcessorTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/graph/GraphProcessorTest.kt
@@ -26,6 +26,7 @@
 import androidx.camera.camera2.pipe.testing.FakeRequestProcessor
 import androidx.camera.camera2.pipe.testing.FakeThreads
 import androidx.camera.camera2.pipe.testing.RobolectricCameraPipeTestRunner
+import androidx.camera.camera2.pipe.testing.awaitEvent
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
@@ -134,7 +135,7 @@
             // processor is set after the requests are submitted.
             graphProcessor.onGraphStarted(fakeProcessor1)
 
-            val event1 = awaitEvent(fakeProcessor1, request1) { it.submit }
+            val event1 = fakeProcessor1.awaitEvent(request = request1) { it.submit }
             assertThat(event1.requestSequence!!.requests).hasSize(1)
             assertThat(event1.requestSequence!!.requests).contains(request1)
 
@@ -160,7 +161,7 @@
 
             graphProcessor.submit(listOf(request1, request2))
             graphProcessor.onGraphStarted(fakeProcessor1)
-            val event = awaitEvent(fakeProcessor1, request1) { it.submit }
+            val event = fakeProcessor1.awaitEvent(request = request1) { it.submit }
             assertThat(event.requestSequence!!.requests).hasSize(2)
             assertThat(event.requestSequence!!.requests).contains(request1)
             assertThat(event.requestSequence!!.requests).contains(request2)
@@ -220,14 +221,14 @@
 
             // Check to make sure that submit is called at least once, and that request1 is rejected
             // from the request processor.
-            awaitEvent(fakeProcessor1, request1) { it.rejected }
+            fakeProcessor1.awaitEvent(request = request1) { it.rejected }
 
             // Stop rejecting requests
             fakeProcessor1.rejectRequests = false
 
             graphProcessor.submit(request2)
             // Cycle events until we get a submitted event with request1
-            val event2 = awaitEvent(fakeProcessor1, request1) { it.submit }
+            val event2 = fakeProcessor1.awaitEvent(request = request1) { it.submit }
             assertThat(event2.rejected).isFalse()
 
             // Assert that immediately after we get a successfully submitted request, the
@@ -253,7 +254,7 @@
             graphProcessor.onGraphStarted(fakeProcessor1)
             graphProcessor.startRepeating(request1)
             graphProcessor.startRepeating(request2)
-            val event = awaitEvent(fakeProcessor1, request2) { it.startRepeating }
+            val event = fakeProcessor1.awaitEvent(request = request2) { it.startRepeating }
             assertThat(event.requestSequence!!.requiredParameters).containsEntry(
                 CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH
             )
@@ -273,10 +274,10 @@
 
             graphProcessor.onGraphStarted(fakeProcessor1)
             graphProcessor.startRepeating(request1)
-            awaitEvent(fakeProcessor1, request1) { it.startRepeating }
+            fakeProcessor1.awaitEvent(request = request1) { it.startRepeating }
 
             graphProcessor.onGraphStarted(fakeProcessor2)
-            awaitEvent(fakeProcessor2, request1) { it.startRepeating }
+            fakeProcessor2.awaitEvent(request = request1) { it.startRepeating }
         }
     }
 
@@ -294,10 +295,10 @@
             fakeProcessor1.rejectRequests = true
             graphProcessor.onGraphStarted(fakeProcessor1)
             graphProcessor.startRepeating(request1)
-            awaitEvent(fakeProcessor1, request1) { it.rejected }
+            fakeProcessor1.awaitEvent(request = request1) { it.rejected }
 
             graphProcessor.onGraphStarted(fakeProcessor2)
-            awaitEvent(fakeProcessor2, request1) { it.startRepeating }
+            fakeProcessor2.awaitEvent(request = request1) { it.startRepeating }
         }
     }
 
@@ -393,24 +394,4 @@
             assertThat(fakeProcessor1.nextEvent().close).isTrue()
         }
     }
-
-    private suspend fun awaitEvent(
-        requestProcessor: FakeRequestProcessor,
-        request: Request,
-        filter: (event: FakeRequestProcessor.Event) -> Boolean
-    ): FakeRequestProcessor.Event {
-
-        var event: FakeRequestProcessor.Event
-        var loopCount = 0
-        while (loopCount < 10) {
-            loopCount++
-            event = requestProcessor.nextEvent()
-            val contains = event.requestSequence?.requests?.contains(request) ?: false
-            if (filter(event) && contains) {
-                return event
-            }
-        }
-
-        throw IllegalStateException("Failed to observe a submit event containing $request")
-    }
 }
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunnerTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt
similarity index 64%
copy from camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunnerTest.kt
copy to camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt
index b5f7555..13a41fc 100644
--- a/camera/camera-camera2-pipe-testing/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunnerTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameraPipeTestRunner.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 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.
@@ -21,7 +21,31 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import org.junit.runners.model.FrameworkMethod
+import org.robolectric.RobolectricTestRunner
 import org.robolectric.annotation.Config
+import org.robolectric.internal.bytecode.InstrumentationConfiguration
+
+/**
+ * A [RobolectricTestRunner] for [androidx.camera.camera2.pipe] unit tests.
+ *
+ * This test runner disables instrumentation for the [androidx.camera.camera2.pipe] and
+ * [androidx.camera.camera2.pipe.testing] packages.
+ *
+ * Robolectric tries to instrument Kotlin classes, and it throws errors when it encounters
+ * companion objects, constructors with default values for parameters, and data classes with
+ * inline classes. We don't need shadowing of our classes because we want to use the actual
+ * objects in our tests.
+ */
+public class RobolectricCameraPipeTestRunner(testClass: Class<*>) :
+    RobolectricTestRunner(testClass) {
+    override fun createClassLoaderConfig(method: FrameworkMethod?): InstrumentationConfiguration {
+        val builder = InstrumentationConfiguration.Builder(super.createClassLoaderConfig(method))
+        builder.doNotInstrumentPackage("androidx.camera.camera2.pipe")
+        builder.doNotInstrumentPackage("androidx.camera.camera2.pipe.testing")
+        return builder.build()
+    }
+}
 
 @Suppress("EXPERIMENTAL_FEATURE_WARNING")
 public inline class TestValue(public val value: String)
diff --git a/camera/camera-camera2-pipe-testing/src/robolectric/java/androidx/camera/camera2/pipe/testing/RobolectricCameras.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameras.kt
similarity index 81%
rename from camera/camera-camera2-pipe-testing/src/robolectric/java/androidx/camera/camera2/pipe/testing/RobolectricCameras.kt
rename to camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameras.kt
index 004cdf4..d21494a 100644
--- a/camera/camera-camera2-pipe-testing/src/robolectric/java/androidx/camera/camera2/pipe/testing/RobolectricCameras.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/testing/RobolectricCameras.kt
@@ -25,14 +25,20 @@
 import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CameraDevice
 import android.hardware.camera2.CameraManager
+import android.os.Build
 import android.os.Handler
 import android.os.Looper
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.CameraMetadata
 import androidx.camera.camera2.pipe.compat.Camera2CameraMetadata
 import androidx.test.core.app.ApplicationProvider
+import com.google.common.truth.Truth
 import kotlinx.atomicfu.atomic
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
 import org.robolectric.Shadows.shadowOf
+import org.robolectric.annotation.Config
 import org.robolectric.shadow.api.Shadow
 import org.robolectric.shadows.ShadowApplication
 import org.robolectric.shadows.ShadowCameraCharacteristics
@@ -169,3 +175,32 @@
         }
     }
 }
+
+@RunWith(RobolectricCameraPipeTestRunner::class)
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class RobolectricCamerasTest {
+    private val context = ApplicationProvider.getApplicationContext() as Context
+    private val mainLooper = shadowOf(Looper.getMainLooper())
+
+    @Test
+    fun fakeCamerasCanBeOpened() {
+        val fakeCameraId = RobolectricCameras.create(
+            mapOf(CameraCharacteristics.LENS_FACING to CameraCharacteristics.LENS_FACING_BACK)
+        )
+        val fakeCamera = RobolectricCameras.open(fakeCameraId)
+
+        Truth.assertThat(fakeCamera).isNotNull()
+        Truth.assertThat(fakeCamera.cameraId).isEqualTo(fakeCameraId)
+        Truth.assertThat(fakeCamera.cameraDevice).isNotNull()
+        Truth.assertThat(fakeCamera.characteristics).isNotNull()
+        Truth.assertThat(fakeCamera.characteristics[CameraCharacteristics.LENS_FACING]).isNotNull()
+        Truth.assertThat(fakeCamera.metadata).isNotNull()
+        Truth.assertThat(fakeCamera.metadata[CameraCharacteristics.LENS_FACING]).isNotNull()
+    }
+
+    @After
+    fun teardown() {
+        mainLooper.idle()
+        RobolectricCameras.clear()
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageCaptureTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageCaptureTest.java
index 2da8952..206c7f2 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageCaptureTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/ImageCaptureTest.java
@@ -16,6 +16,8 @@
 
 package androidx.camera.camera2;
 
+import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_USE_SOFTWARE_JPEG_ENCODER;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -41,6 +43,7 @@
 import android.hardware.camera2.TotalCaptureResult;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.location.Location;
+import android.media.ImageWriter;
 import android.net.Uri;
 import android.os.Environment;
 import android.provider.MediaStore;
@@ -70,6 +73,7 @@
 import androidx.camera.core.impl.CaptureStage;
 import androidx.camera.core.impl.ImageCaptureConfig;
 import androidx.camera.core.impl.ImageOutputConfig;
+import androidx.camera.core.impl.ImageProxyBundle;
 import androidx.camera.core.impl.UseCaseConfig;
 import androidx.camera.core.impl.utils.CameraOrientationUtil;
 import androidx.camera.core.impl.utils.Exif;
@@ -81,9 +85,12 @@
 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.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.GrantPermissionRule;
 
+import com.google.common.util.concurrent.ListenableFuture;
+
 import org.junit.After;
 import org.junit.Assume;
 import org.junit.Before;
@@ -1224,6 +1231,134 @@
         assertThat(imageCapture.getFlashMode()).isEqualTo(ImageCapture.FLASH_MODE_ON);
     }
 
+    @Test
+    // Output JPEG format image when setting a CaptureProcessor is only enabled for devices that
+    // API level is at least 29.
+    @SdkSuppress(minSdkVersion = 29)
+    public void returnJpegImage_whenSoftwareJpegIsEnabled()
+            throws ExecutionException, InterruptedException {
+        assumeTrue(CameraUtil.hasCameraWithLensFacing(CameraSelector.LENS_FACING_BACK));
+
+        ImageCapture.Builder builder = new ImageCapture.Builder();
+
+        // Enables software Jpeg
+        builder.getMutableConfig().insertOption(OPTION_USE_SOFTWARE_JPEG_ENCODER, true);
+
+        ImageCapture useCase = builder.build();
+
+        mCamera = CameraUtil.createCameraAndAttachUseCase(mContext,
+                CameraSelector.DEFAULT_BACK_CAMERA, useCase);
+
+        ResolvableFuture<ImageProperties> imageProperties = ResolvableFuture.create();
+        OnImageCapturedCallback callback = createMockOnImageCapturedCallback(imageProperties);
+        useCase.takePicture(mMainExecutor, callback);
+        // Wait for the signal that the image has been captured.
+        verify(callback, timeout(10000)).onCaptureSuccess(any(ImageProxy.class));
+
+        // Check the output image rotation degrees value is correct.
+        assertThat(imageProperties.get().rotationDegrees).isEqualTo(
+                mCamera.getCameraInfo().getSensorRotationDegrees(useCase.getTargetRotation()));
+        // Check the output format is correct.
+        assertThat(imageProperties.get().format).isEqualTo(ImageFormat.JPEG);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 26)
+    public void returnYuvImage_whenSoftwareJpegIsEnabledWithYuvBufferFormat()
+            throws ExecutionException, InterruptedException {
+        assumeTrue(CameraUtil.hasCameraWithLensFacing(CameraSelector.LENS_FACING_BACK));
+
+        ImageCapture.Builder builder = new ImageCapture.Builder().setBufferFormat(
+                ImageFormat.YUV_420_888);
+
+        // Enables software Jpeg
+        builder.getMutableConfig().insertOption(OPTION_USE_SOFTWARE_JPEG_ENCODER, true);
+
+        ImageCapture useCase = builder.build();
+
+        mCamera = CameraUtil.createCameraAndAttachUseCase(mContext,
+                CameraSelector.DEFAULT_BACK_CAMERA, useCase);
+
+        ResolvableFuture<ImageProperties> imageProperties = ResolvableFuture.create();
+        OnImageCapturedCallback callback = createMockOnImageCapturedCallback(imageProperties);
+        useCase.takePicture(mMainExecutor, callback);
+        // Wait for the signal that the image has been captured.
+        verify(callback, timeout(10000)).onCaptureSuccess(any(ImageProxy.class));
+
+        // Check the output image rotation degrees value is correct.
+        assertThat(imageProperties.get().rotationDegrees).isEqualTo(
+                mCamera.getCameraInfo().getSensorRotationDegrees(useCase.getTargetRotation()));
+        // Check the output format is correct.
+        assertThat(imageProperties.get().format).isEqualTo(ImageFormat.YUV_420_888);
+    }
+
+    @Test
+    // Output JPEG format image when setting a CaptureProcessor is only enabled for devices that
+    // API level is at least 29.
+    @SdkSuppress(minSdkVersion = 29)
+    public void returnJpegImage_whenCaptureProcessorIsSet() throws ExecutionException,
+            InterruptedException {
+        assumeTrue(CameraUtil.hasCameraWithLensFacing(CameraSelector.LENS_FACING_BACK));
+
+        ImageCapture.Builder builder = new ImageCapture.Builder();
+        SimpleCaptureProcessor simpleCaptureProcessor = new SimpleCaptureProcessor();
+
+        // Set a CaptureProcessor to directly pass the image to output surface.
+        ImageCapture useCase = builder.setCaptureProcessor(simpleCaptureProcessor).build();
+
+        mCamera = CameraUtil.createCameraAndAttachUseCase(mContext,
+                CameraSelector.DEFAULT_BACK_CAMERA, useCase);
+
+        ResolvableFuture<ImageProperties> imageProperties = ResolvableFuture.create();
+        OnImageCapturedCallback callback = createMockOnImageCapturedCallback(imageProperties);
+        useCase.takePicture(mMainExecutor, callback);
+        // Wait for the signal that the image has been captured.
+        verify(callback, timeout(10000)).onCaptureSuccess(any(ImageProxy.class));
+
+        // Check the output image rotation degrees value is correct.
+        assertThat(imageProperties.get().rotationDegrees).isEqualTo(
+                mCamera.getCameraInfo().getSensorRotationDegrees(useCase.getTargetRotation()));
+        // Check the output format is correct.
+        assertThat(imageProperties.get().format).isEqualTo(ImageFormat.JPEG);
+
+        simpleCaptureProcessor.close();
+    }
+
+    @Test
+    // Output JPEG format image when setting a CaptureProcessor is only enabled for devices that
+    // API level is at least 29.
+    @SdkSuppress(minSdkVersion = 29)
+    public void returnJpegImage_whenSoftwareJpegIsEnabledWithCaptureProcessor()
+            throws ExecutionException, InterruptedException {
+        assumeTrue(CameraUtil.hasCameraWithLensFacing(CameraSelector.LENS_FACING_BACK));
+
+        ImageCapture.Builder builder = new ImageCapture.Builder();
+        SimpleCaptureProcessor simpleCaptureProcessor = new SimpleCaptureProcessor();
+
+        // Set a CaptureProcessor to directly pass the image to output surface.
+        ImageCapture useCase = builder.setCaptureProcessor(simpleCaptureProcessor).build();
+
+        // Enables software Jpeg
+        builder.getMutableConfig().insertOption(OPTION_USE_SOFTWARE_JPEG_ENCODER, true);
+
+        mCamera = CameraUtil.createCameraAndAttachUseCase(mContext,
+                CameraSelector.DEFAULT_BACK_CAMERA, useCase);
+
+        ResolvableFuture<ImageProperties> imageProperties = ResolvableFuture.create();
+        OnImageCapturedCallback callback = createMockOnImageCapturedCallback(imageProperties);
+        useCase.takePicture(mMainExecutor, callback);
+        // Wait for the signal that the image has been captured.
+        verify(callback, timeout(10000)).onCaptureSuccess(any(ImageProxy.class));
+
+        // Check the output image rotation degrees value is correct.
+        assertThat(imageProperties.get().rotationDegrees).isEqualTo(
+                mCamera.getCameraInfo().getSensorRotationDegrees(useCase.getTargetRotation()));
+        // Check the output format is correct.
+        assertThat(imageProperties.get().format).isEqualTo(ImageFormat.JPEG);
+
+        simpleCaptureProcessor.close();
+    }
+
     private OnImageCapturedCallback createMockOnImageCapturedCallback(
             @Nullable ResolvableFuture<ImageProperties> resultProperties) {
         OnImageCapturedCallback callback = mock(OnImageCapturedCallback.class);
@@ -1307,4 +1442,38 @@
             mCountDownLatch.countDown();
         }
     }
+
+    private static class SimpleCaptureProcessor implements CaptureProcessor {
+        private ImageWriter mImageWriter = null;
+
+        @Override
+        public void onOutputSurface(Surface surface, int imageFormat) {
+            mImageWriter = ImageWriter.newInstance(surface, 2);
+        }
+
+        @Override
+        public void process(ImageProxyBundle bundle) {
+            ListenableFuture<ImageProxy> imageProxyListenableFuture =
+                    bundle.getImageProxy(bundle.getCaptureIds().get(0));
+            try {
+                ImageProxy imageProxy = imageProxyListenableFuture.get();
+                // Directly passing the input YUV image to the output surface.
+                mImageWriter.queueInputImage(imageProxy.getImage());
+            } catch (ExecutionException | InterruptedException e) {
+                throw new IllegalArgumentException("Can't extract ImageProxy from the"
+                        + " ImageProxyBundle." + e);
+            }
+        }
+
+        @Override
+        public void onResolutionUpdate(Size size) {
+
+        }
+
+        public void close() {
+            if (mImageWriter != null) {
+                mImageWriter.close();
+            }
+        }
+    }
 }
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/CaptureSessionTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/CaptureSessionTest.java
index 20e8154..9127af3 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/CaptureSessionTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/CaptureSessionTest.java
@@ -74,8 +74,10 @@
 import com.google.common.util.concurrent.ListenableFuture;
 
 import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.AssumptionViolatedException;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -107,7 +109,7 @@
 @RunWith(AndroidJUnit4.class)
 public final class CaptureSessionTest {
     /** Thread for all asynchronous calls. */
-    private HandlerThread mHandlerThread;
+    private static HandlerThread sHandlerThread;
     /** Handler for all asynchronous calls. */
     private Handler mHandler;
     /** Executor which delegates to Handler */
@@ -128,14 +130,25 @@
     @Rule
     public TestRule mUseCameraRule = CameraUtil.grantCameraPermissionAndPreTest();
 
+    @BeforeClass
+    public static void setUpClass() {
+        sHandlerThread = new HandlerThread("CaptureSessionTest");
+        sHandlerThread.start();
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+        if (sHandlerThread != null) {
+            sHandlerThread.quitSafely();
+        }
+    }
+
     @Before
     public void setup() throws CameraAccessException, InterruptedException,
             AssumptionViolatedException, TimeoutException, ExecutionException {
         mTestParameters0 = new CaptureSessionTestParameters("mTestParameters0");
         mTestParameters1 = new CaptureSessionTestParameters("mTestParameters1");
-        mHandlerThread = new HandlerThread("CaptureSessionTest");
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
+        mHandler = new Handler(sHandlerThread.getLooper());
 
         mExecutor = CameraXExecutors.newHandlerExecutor(mHandler);
         mScheduledExecutor = CameraXExecutors.newHandlerExecutor(mHandler);
@@ -166,10 +179,6 @@
             mTestParameters0.tearDown();
             mTestParameters1.tearDown();
         }
-
-        if (mHandlerThread != null) {
-            mHandlerThread.quitSafely();
-        }
     }
 
     @Test
@@ -666,7 +675,7 @@
         CaptureResult captureResult2 = ((Camera2CameraCaptureResult) result2).getCaptureResult();
         assertThat(
                 captureResult2.getRequest().get(CaptureRequest.CONTROL_CAPTURE_INTENT)).isEqualTo(
-                CaptureRequest.CONTROL_CAPTURE_INTENT_ZERO_SHUTTER_LAG);
+                CaptureRequest.CONTROL_CAPTURE_INTENT_CUSTOM);
         // The onEnableSession should not been invoked in close().
         verify(mTestParameters0.mTestCameraEventCallback.mEnableCallback,
                 never()).onCaptureCompleted(any(CameraCaptureResult.class));
@@ -1171,7 +1180,7 @@
         @Override
         public CaptureConfig onDisableSession() {
             return getCaptureConfig(CaptureRequest.CONTROL_CAPTURE_INTENT,
-                    CaptureRequest.CONTROL_CAPTURE_INTENT_ZERO_SHUTTER_LAG, mDisableCallback);
+                    CaptureRequest.CONTROL_CAPTURE_INTENT_CUSTOM, mDisableCallback);
         }
     }
 
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CameraSelectionOptimizer.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CameraSelectionOptimizer.java
index 47fad26..c402e97 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CameraSelectionOptimizer.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CameraSelectionOptimizer.java
@@ -53,9 +53,16 @@
             }
 
             // Skip camera ID by heuristic: 0 is back lens facing, 1 is front lens facing.
-            Integer lensFacingInteger = availableCamerasSelector.getLensFacing();
-            String skippedCameraId = decideSkippedCameraIdByHeuristic(
-                    cameraFactory.getCameraManager(), lensFacingInteger, cameraIdList);
+            String skippedCameraId;
+            try {
+                Integer lensFacingInteger = availableCamerasSelector.getLensFacing();
+                skippedCameraId = decideSkippedCameraIdByHeuristic(
+                        cameraFactory.getCameraManager(), lensFacingInteger, cameraIdList);
+            } catch (IllegalStateException e) {
+                // Don't skip camera if there is any conflict in camera lens facing.
+                skippedCameraId = null;
+            }
+
             List<CameraInfo> cameraInfos = new ArrayList<>();
 
             for (String id : cameraIdList) {
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
index 6e155b8..a07b42b 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/CaptureSession.java
@@ -908,7 +908,6 @@
                     case INITIALIZED:
                     case GET_SURFACE:
                     case OPENED:
-                    case RELEASED:
                         throw new IllegalStateException(
                                 "onConfigureFailed() should not be possible in state: " + mState);
                     case OPENING:
@@ -920,6 +919,9 @@
                         // internally. Check b/147402661 for detail.
                         finishClose();
                         break;
+                    case RELEASED:
+                        Logger.d(TAG, "ConfigureFailed callback after change to RELEASED state");
+                        break;
                 }
                 Logger.e(TAG, "CameraCaptureSession.onConfigureFailed() " + mState);
             }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
index 9f0cacf..8bcdb97 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraX.java
@@ -598,7 +598,8 @@
                 // Only verify the devices might have the b/167201193
                 if (DeviceQuirks.get(IncompleteCameraListQuirk.class) != null) {
                     // Please ensure only validate the camera at the last of the initialization.
-                    CameraValidator.validateCameras(mAppContext, mCameraRepository);
+                    CameraValidator.validateCameras(mAppContext, mCameraRepository,
+                            availableCamerasLimiter);
                 }
 
                 // Set completer to null if the init was successful.
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CaptureProcessorPipeline.java b/camera/camera-core/src/main/java/androidx/camera/core/CaptureProcessorPipeline.java
new file mode 100644
index 0000000..d7856e9
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CaptureProcessorPipeline.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core;
+
+import android.graphics.ImageFormat;
+import android.media.ImageReader;
+import android.util.Size;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.camera.core.impl.CaptureProcessor;
+import androidx.camera.core.impl.ImageProxyBundle;
+import androidx.camera.core.impl.ImageReaderProxy;
+import androidx.camera.core.impl.TagBundle;
+import androidx.core.util.Preconditions;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+
+/**
+ * A CaptureProcessor which can link two CaptureProcessors.
+ */
+class CaptureProcessorPipeline implements CaptureProcessor {
+    private final CaptureProcessor mPreCaptureProcessor;
+    private final CaptureProcessor mPostCaptureProcessor;
+    private final Executor mExecutor;
+    private final int mMaxImages;
+    private ImageReaderProxy mIntermediateImageReader = null;
+    private ImageInfo mSourceImageInfo = null;
+
+    /**
+     * Creates a {@link CaptureProcessorPipeline} to link two CaptureProcessors to process the
+     * captured images.
+     *
+     * @param preCaptureProcessor  The pre-processing {@link CaptureProcessor} which must output
+     *                             YUV_420_888 {@link ImageProxy} for the post-processing
+     *                             {@link CaptureProcessor} to process.
+     * @param maxImages            the maximum image buffer count used to create the intermediate
+     *                             {@link ImageReaderProxy} to receive the processed
+     *                             {@link ImageProxy} from the
+     *                             pre-processing {@link CaptureProcessor}.
+     * @param postCaptureProcessor The post-processing {@link CaptureProcessor} which can process
+     *                             an {@link ImageProxy} of YUV_420_888 format . It must be able
+     *                             to process the image without referencing to the
+     *                             {@link TagBundle} and capture id.
+     * @param executor             the {@link Executor} used by the post-processing
+     * {@link CaptureProcessor}
+     *                             to process the {@link ImageProxy}.
+     */
+    CaptureProcessorPipeline(@NonNull CaptureProcessor preCaptureProcessor, int maxImages,
+            @NonNull CaptureProcessor postCaptureProcessor, @NonNull Executor executor) {
+        mPreCaptureProcessor = preCaptureProcessor;
+        mPostCaptureProcessor = postCaptureProcessor;
+        mExecutor = executor;
+        mMaxImages = maxImages;
+    }
+
+    @Override
+    public void onOutputSurface(@NonNull Surface surface, int imageFormat) {
+        // Updates the output surface to the post-processing CaptureProcessor
+        mPostCaptureProcessor.onOutputSurface(surface, imageFormat);
+    }
+
+    @Override
+    public void process(@NonNull ImageProxyBundle bundle) {
+        List<Integer> ids = bundle.getCaptureIds();
+        ListenableFuture<ImageProxy> imageProxyListenableFuture = bundle.getImageProxy(ids.get(0));
+        Preconditions.checkArgument(imageProxyListenableFuture.isDone());
+
+        ImageProxy imageProxy;
+        try {
+            imageProxy = imageProxyListenableFuture.get();
+            ImageInfo imageInfo = imageProxy.getImageInfo();
+            // Stores the imageInfo of source image that will be used when when the processed
+            // ImageProxy is received from the pre-processing CaptureProcessor.
+            mSourceImageInfo = imageInfo;
+        } catch (ExecutionException | InterruptedException e) {
+            throw new IllegalArgumentException("Can not successfully extract ImageProxy from the "
+                    + "ImageProxyBundle.");
+        }
+
+        // Calls the pre-processing CaptureProcessor to process the ImageProxyBundle
+        mPreCaptureProcessor.process(bundle);
+    }
+
+    @Override
+    public void onResolutionUpdate(@NonNull Size size) {
+        // Creates an intermediate ImageReader to receive the processed image from the
+        // pre-processing CaptureProcessor.
+        mIntermediateImageReader = new AndroidImageReaderProxy(
+                ImageReader.newInstance(size.getWidth(), size.getHeight(),
+                        ImageFormat.YUV_420_888, mMaxImages));
+        mPreCaptureProcessor.onOutputSurface(mIntermediateImageReader.getSurface(),
+                ImageFormat.YUV_420_888);
+        mPreCaptureProcessor.onResolutionUpdate(size);
+
+        // Updates the resolution information to the post-processing CaptureProcessor.
+        mPostCaptureProcessor.onResolutionUpdate(size);
+
+        // Register the ImageAvailableListener to receive the processed image from the
+        // pre-processing CaptureProcessor.
+        mIntermediateImageReader.setOnImageAvailableListener(
+                new ImageReaderProxy.OnImageAvailableListener() {
+                    @Override
+                    public void onImageAvailable(@NonNull ImageReaderProxy imageReader) {
+                        postProcess(imageReader.acquireNextImage());
+                    }
+                }, mExecutor);
+    }
+
+    void postProcess(ImageProxy imageProxy) {
+        Size resolution = new Size(imageProxy.getWidth(), imageProxy.getHeight());
+
+        // Retrieves information from ImageInfo of source image to create a
+        // SettableImageProxyBundle and calls the post-processing CaptureProcessor to process it.
+        Preconditions.checkNotNull(mSourceImageInfo);
+        String tagBundleKey = mSourceImageInfo.getTagBundle().listKeys().iterator().next();
+        int captureId = mSourceImageInfo.getTagBundle().getTag(tagBundleKey);
+        SettableImageProxy settableImageProxy =
+                new SettableImageProxy(imageProxy, resolution, mSourceImageInfo);
+        mSourceImageInfo = null;
+
+        SettableImageProxyBundle settableImageProxyBundle = new SettableImageProxyBundle(
+                Collections.singletonList(captureId), tagBundleKey);
+        settableImageProxyBundle.addImageProxy(settableImageProxy);
+        mPostCaptureProcessor.process(settableImageProxyBundle);
+    }
+
+    /**
+     * Closes the objects generated when creating the {@link CaptureProcessorPipeline}.
+     */
+    void close() {
+        if (mIntermediateImageReader != null) {
+            mIntermediateImageReader.clearOnImageAvailableListener();
+            mIntermediateImageReader.close();
+        }
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
index 03c66f6..2bd0527 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
@@ -372,17 +372,26 @@
         } else if (mCaptureProcessor != null || mUseSoftwareJpeg) {
             // Capture processor set from configuration takes precedence over software JPEG.
             YuvToJpegProcessor softwareJpegProcessor = null;
+            CaptureProcessorPipeline captureProcessorPipeline = null;
             CaptureProcessor captureProcessor = mCaptureProcessor;
             int inputFormat = getImageFormat();
             int outputFormat = getImageFormat();
             if (mUseSoftwareJpeg) {
-                Preconditions.checkState(mCaptureProcessor == null, "CaptureProcessor should not "
-                        + "be set if software JPEG is to be used.");
                 // API check to satisfy linter
                 if (Build.VERSION.SDK_INT >= 26) {
                     Logger.i(TAG, "Using software JPEG encoder.");
-                    captureProcessor = softwareJpegProcessor =
-                            new YuvToJpegProcessor(getJpegQuality(), mMaxCaptureStages);
+
+                    if (mCaptureProcessor != null) {
+                        softwareJpegProcessor = new YuvToJpegProcessor(getJpegQuality(),
+                                mMaxCaptureStages);
+                        captureProcessor = captureProcessorPipeline = new CaptureProcessorPipeline(
+                                mCaptureProcessor, mMaxCaptureStages, softwareJpegProcessor,
+                                mExecutor);
+                    } else {
+                        captureProcessor = softwareJpegProcessor =
+                                new YuvToJpegProcessor(getJpegQuality(), mMaxCaptureStages);
+                    }
+
                     outputFormat = ImageFormat.JPEG;
                 } else {
                     // Note: This should never be hit due to SDK_INT check before setting
@@ -408,10 +417,12 @@
                 // Close the JPEG processor once ProcessingImageReader is done.
                 // Processor is assigned to an effectively final variable here for the lambda.
                 YuvToJpegProcessor processorToClose = softwareJpegProcessor;
+                CaptureProcessorPipeline captureProcessorPipelineToClose = captureProcessorPipeline;
                 mProcessingImageReader.getCloseFuture().addListener(() -> {
                     // API check to satisfy linter
                     if (Build.VERSION.SDK_INT >= 26) {
                         processorToClose.close();
+                        captureProcessorPipelineToClose.close();
                     }
                 }, CameraXExecutors.directExecutor());
             }
@@ -512,9 +523,16 @@
     @Override
     UseCaseConfig<?> onMergeConfig(@NonNull CameraInfoInternal cameraInfo,
             @NonNull UseCaseConfig.Builder<?, ?, ?> builder) {
-        // Request software JPEG encoder if quirk exists on this device and the software JPEG
-        // option has not already been explicitly set.
-        if (cameraInfo.getCameraQuirks().contains(SoftwareJpegEncodingPreferredQuirk.class)) {
+        if (builder.getUseCaseConfig().retrieveOption(OPTION_CAPTURE_PROCESSOR, null)
+                != null && Build.VERSION.SDK_INT >= 29) {
+            // TODO: The API level check can be removed if the ImageWriterCompat issue on API
+            //  level 28 devices (b182363220/) can be resolved.
+            Logger.i(TAG, "Requesting software JPEG due to a CaptureProcessor is set.");
+            builder.getMutableConfig().insertOption(OPTION_USE_SOFTWARE_JPEG_ENCODER, true);
+        } else if (cameraInfo.getCameraQuirks().contains(
+                SoftwareJpegEncodingPreferredQuirk.class)) {
+            // Request software JPEG encoder if quirk exists on this device and the software JPEG
+            // option has not already been explicitly set.
             if (!builder.getMutableConfig().retrieveOption(OPTION_USE_SOFTWARE_JPEG_ENCODER,
                     true)) {
                 Logger.w(TAG, "Device quirk suggests software JPEG encoder, but it has been "
@@ -1208,11 +1226,6 @@
                 supported = false;
             }
 
-            if (mutableConfig.retrieveOption(OPTION_CAPTURE_PROCESSOR, null) != null) {
-                Logger.w(TAG, "CaptureProcessor is set, unable to use software JPEG.");
-                supported = false;
-            }
-
             if (!supported) {
                 Logger.w(TAG, "Unable to support software JPEG. Disabling.");
                 mutableConfig.insertOption(OPTION_USE_SOFTWARE_JPEG_ENCODER, false);
@@ -1491,21 +1504,19 @@
         if (mProcessingImageReader != null) {
             // If the Processor is provided, check if we have valid CaptureBundle and update
             // ProcessingImageReader before actually issuing a take picture request.
-            if (mUseSoftwareJpeg) {
-                captureBundle = getCaptureBundle(CaptureBundles.singleDefaultCaptureBundle());
-                if (captureBundle.getCaptureStages().size() > 1) {
-                    return Futures.immediateFailedFuture(new IllegalArgumentException(
-                            "Software JPEG not supported with CaptureBundle size > 1."));
-                }
-            } else {
-                captureBundle = getCaptureBundle(null);
-            }
+            captureBundle = getCaptureBundle(CaptureBundles.singleDefaultCaptureBundle());
 
             if (captureBundle == null) {
                 return Futures.immediateFailedFuture(new IllegalArgumentException(
                         "ImageCapture cannot set empty CaptureBundle."));
             }
 
+            if (mCaptureProcessor == null && captureBundle.getCaptureStages().size() > 1) {
+                return Futures.immediateFailedFuture(new IllegalArgumentException(
+                        "No CaptureProcessor can be found to process the images captured for "
+                                + "multiple CaptureStages."));
+            }
+
             if (captureBundle.getCaptureStages().size() > mMaxCaptureStages) {
                 return Futures.immediateFailedFuture(new IllegalArgumentException(
                         "ImageCapture has CaptureStages > Max CaptureStage size"));
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraValidator.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraValidator.java
index b2bddec..2eb7b46 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraValidator.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraValidator.java
@@ -41,23 +41,52 @@
      * physically supported device lens facing information comes from the package manager and the
      * system feature flags are set by the vendor as part of the device build and CTS verified.
      *
-     * @param context          The application or activity context.
-     * @param cameraRepository The camera repository for verify.
+     * @param context                  The application or activity context.
+     * @param cameraRepository         The camera repository for verify.
+     * @param availableCamerasSelector Indicate the camera that we need to check.
      * @throws CameraIdListIncorrectException if it fails to find all the camera instances that
-     * physically supported on the device.
+     *                                        physically supported on the device.
      */
     public static void validateCameras(@NonNull Context context,
-            @NonNull CameraRepository cameraRepository) throws CameraIdListIncorrectException {
+            @NonNull CameraRepository cameraRepository,
+            @Nullable CameraSelector availableCamerasSelector)
+            throws CameraIdListIncorrectException {
+
+        Integer lensFacing = null;
+        try {
+            if (availableCamerasSelector != null
+                    && (lensFacing = availableCamerasSelector.getLensFacing()) == null) {
+                Logger.w(TAG, "No lens facing info in the availableCamerasSelector, don't "
+                        + "verify the camera lens facing.");
+                return;
+            }
+        } catch (IllegalStateException e) {
+            Logger.e(TAG, "Cannot get lens facing from the availableCamerasSelector don't "
+                    + "verify the camera lens facing.", e);
+            return;
+        }
+
+        Logger.d(TAG,
+                "Verifying camera lens facing on " + Build.DEVICE + ", lensFacingInteger: "
+                        + lensFacing);
 
         PackageManager pm = context.getPackageManager();
-
-        Logger.d(TAG, "Verifying camera lens facing on " + Build.DEVICE);
         try {
             if (pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
-                CameraSelector.DEFAULT_BACK_CAMERA.select(cameraRepository.getCameras());
+                if (availableCamerasSelector == null
+                        || lensFacing.intValue() == CameraSelector.LENS_FACING_BACK) {
+                    // Only verify the main camera if it is NOT specifying the available lens
+                    // facing or it required the LENS_FACING_BACK camera.
+                    CameraSelector.DEFAULT_BACK_CAMERA.select(cameraRepository.getCameras());
+                }
             }
             if (pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)) {
-                CameraSelector.DEFAULT_FRONT_CAMERA.select(cameraRepository.getCameras());
+                if (availableCamerasSelector == null
+                        || lensFacing.intValue() == CameraSelector.LENS_FACING_FRONT) {
+                    // Only verify the front camera if it is NOT specifying the available lens
+                    // facing or it required the LENS_FACING_FRONT camera.
+                    CameraSelector.DEFAULT_FRONT_CAMERA.select(cameraRepository.getCameras());
+                }
             }
         } catch (IllegalArgumentException e) {
             Logger.e(TAG, "Camera LensFacing verification failed, existing cameras: "
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CaptureProcessor.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CaptureProcessor.java
index 322719d..54c7037b 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CaptureProcessor.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CaptureProcessor.java
@@ -19,7 +19,9 @@
 import android.util.Size;
 import android.view.Surface;
 
+import androidx.camera.core.ImageCapture;
 import androidx.camera.core.ImageProxy;
+import androidx.camera.core.Preview;
 
 /**
  * A processing step of the image capture pipeline.
@@ -38,8 +40,9 @@
      * Process a {@link ImageProxyBundle} for the set of captures that were
      * requested.
      *
-     * <p> The result of the processing step should be written to the {@link Surface} that was
-     * received by {@link #onOutputSurface(Surface, int)}.
+     * <p> A result of the processing step must be written to the {@link Surface} that was
+     * received by {@link #onOutputSurface(Surface, int)}. Otherwise, it might cause the
+     * {@link ImageCapture#takePicture} can't be complete or frame lost in {@link Preview}.
      * @param bundle The set of images to process. The ImageProxyBundle and the {@link ImageProxy}
      *               that are retrieved from it will become invalid after this method completes, so
      *               no references to them should be kept.
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/ImageWriterCompat.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/ImageWriterCompat.java
index 773b1ad..452de69 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/ImageWriterCompat.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/ImageWriterCompat.java
@@ -74,10 +74,10 @@
     @NonNull
     public static ImageWriter newInstance(@NonNull Surface surface,
             @IntRange(from = 1) int maxImages, int format) {
-        if (Build.VERSION.SDK_INT >= 26) {
-            return ImageWriterCompatApi26Impl.newInstance(surface, maxImages, format);
-        } else if (Build.VERSION.SDK_INT >= 29) {
+        if (Build.VERSION.SDK_INT >= 29) {
             return ImageWriterCompatApi29Impl.newInstance(surface, maxImages, format);
+        } else if (Build.VERSION.SDK_INT >= 26) {
+            return ImageWriterCompatApi26Impl.newInstance(surface, maxImages, format);
         }
 
         throw new RuntimeException(
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/IncompleteCameraListQuirk.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/IncompleteCameraListQuirk.java
index f013184..35779f17 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/IncompleteCameraListQuirk.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/IncompleteCameraListQuirk.java
@@ -41,7 +41,8 @@
                     "vox_alpha_plus", "a5y17ltecan", "x304l", "hero2qltevzw", "a5y17lteskt",
                     "1801", "a5y17lteskt", "1801", "a5y17ltelgt", "herolte", "htc_hiau_ml_tuhl",
                     "a6plte", "hwtrt-q", "co2_sprout", "h3223", "davinci", "vince", "armor_x5",
-                    "a2corelte", "j6lte"));
+                    "a2corelte", "j6lte", "walleye", "taimen", "blueline", "crosshatch", "bonito",
+                    "sargo", "coral", "flame", "sunfish", "bramble", "redfin"));
 
     static boolean load() {
         return KNOWN_AFFECTED_DEVICES.contains(Build.DEVICE.toLowerCase(Locale.getDefault()));
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewTest.java b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewTest.java
index 509895a..03776b3 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewTest.java
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewTest.java
@@ -24,11 +24,9 @@
 import static org.junit.Assume.assumeTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.app.Instrumentation;
 import android.content.Context;
@@ -56,6 +54,7 @@
 import androidx.camera.core.Preview;
 import androidx.camera.core.SurfaceRequest;
 import androidx.camera.core.ViewPort;
+import androidx.camera.core.impl.CameraInfoInternal;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.camera.core.impl.utils.futures.FutureCallback;
 import androidx.camera.core.impl.utils.futures.Futures;
@@ -181,7 +180,8 @@
             previewView.set(new PreviewView(mContext));
             setContentView(previewView.get());
             // Feed the PreviewView with a fake SurfaceRequest
-            CameraInfo cameraInfo = createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2);
+            CameraInfoInternal cameraInfo =
+                    createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2);
             previewView.get().getSurfaceProvider().onSurfaceRequested(
                     createSurfaceRequest(cameraInfo));
             notifyLatchWhenLayoutReady(previewView.get(), countDownLatch);
@@ -358,7 +358,7 @@
     @Test
     @UiThreadTest
     public void usesTextureView_whenLegacyDevice() {
-        final CameraInfo cameraInfo =
+        final CameraInfoInternal cameraInfo =
                 createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2_LEGACY);
         final PreviewView previewView = new PreviewView(mContext);
         setContentView(previewView);
@@ -373,7 +373,8 @@
     @UiThreadTest
     public void usesTextureView_whenAPILevelNotNewerThanN() {
         assumeTrue(Build.VERSION.SDK_INT <= 24);
-        final CameraInfo cameraInfo = createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2);
+        final CameraInfoInternal cameraInfo =
+                createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2);
 
         final PreviewView previewView = new PreviewView(mContext);
         setContentView(previewView);
@@ -388,7 +389,8 @@
     @UiThreadTest
     public void usesSurfaceView_whenNonLegacyDevice_andAPILevelNewerThanN() {
         assumeTrue(Build.VERSION.SDK_INT > 24);
-        final CameraInfo cameraInfo = createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2);
+        final CameraInfoInternal cameraInfo =
+                createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2);
 
         final PreviewView previewView = new PreviewView(mContext);
         setContentView(previewView);
@@ -402,7 +404,8 @@
     @Test
     @UiThreadTest
     public void usesTextureView_whenNonLegacyDevice_andImplModeIsTextureView() {
-        final CameraInfo cameraInfo = createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2);
+        final CameraInfoInternal cameraInfo =
+                createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2);
 
         final PreviewView previewView = new PreviewView(mContext);
         setContentView(previewView);
@@ -415,7 +418,8 @@
 
     @Test
     public void correctSurfacePixelFormat_whenRGBA8888IsRequired() throws Throwable {
-        final CameraInfo cameraInfo = createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2);
+        final CameraInfoInternal cameraInfo =
+                createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2);
         SurfaceRequest surfaceRequest = createRgb8888SurfaceRequest(cameraInfo);
         ListenableFuture<Surface> future = surfaceRequest.getDeferrableSurface().getSurface();
 
@@ -448,7 +452,7 @@
 
     @Test
     public void canCreateValidMeteringPoint() throws Exception {
-        final CameraInfo cameraInfo = createCameraInfo(90,
+        final CameraInfoInternal cameraInfo = createCameraInfo(90,
                 CameraInfo.IMPLEMENTATION_TYPE_CAMERA2, CameraSelector.LENS_FACING_BACK);
 
         CountDownLatch countDownLatch = new CountDownLatch(1);
@@ -478,7 +482,7 @@
 
     @Test
     public void meteringPointFactoryAutoAdjusted_whenViewSizeChange() throws Exception {
-        final CameraInfo cameraInfo = createCameraInfo(90,
+        final CameraInfoInternal cameraInfo = createCameraInfo(90,
                 CameraInfo.IMPLEMENTATION_TYPE_CAMERA2, CameraSelector.LENS_FACING_BACK);
 
         mInstrumentation.runOnMainSync(() -> {
@@ -528,7 +532,7 @@
 
     @Test
     public void meteringPointFactoryAutoAdjusted_whenScaleTypeChanged() throws Exception {
-        final CameraInfo cameraInfo = createCameraInfo(90,
+        final CameraInfoInternal cameraInfo = createCameraInfo(90,
                 CameraInfo.IMPLEMENTATION_TYPE_CAMERA2, CameraSelector.LENS_FACING_BACK);
         mInstrumentation.runOnMainSync(() -> {
             mPreviewView = new PreviewView(mContext);
@@ -558,9 +562,9 @@
 
     @Test
     public void meteringPointFactoryAutoAdjusted_whenTransformationInfoChanged() throws Exception {
-        final CameraInfo cameraInfo1 = createCameraInfo(90,
+        final CameraInfoInternal cameraInfo1 = createCameraInfo(90,
                 CameraInfo.IMPLEMENTATION_TYPE_CAMERA2, CameraSelector.LENS_FACING_BACK);
-        final CameraInfo cameraInfo2 = createCameraInfo(270,
+        final CameraInfoInternal cameraInfo2 = createCameraInfo(270,
                 CameraInfo.IMPLEMENTATION_TYPE_CAMERA2, CameraSelector.LENS_FACING_FRONT);
 
         mInstrumentation.runOnMainSync(() -> {
@@ -614,7 +618,7 @@
     @Test
     @UiThreadTest
     public void meteringPointInvalid_whenPreviewViewWidthOrHeightIs0() {
-        final CameraInfo cameraInfo = createCameraInfo(90,
+        final CameraInfoInternal cameraInfo = createCameraInfo(90,
                 CameraInfo.IMPLEMENTATION_TYPE_CAMERA2, CameraSelector.LENS_FACING_BACK);
 
         final PreviewView previewView = new PreviewView(mContext);
@@ -818,7 +822,8 @@
 
         // Start a preview stream
         final Preview.SurfaceProvider surfaceProvider = previewView.getSurfaceProvider();
-        final CameraInfo cameraInfo = createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2);
+        final CameraInfoInternal cameraInfo =
+                createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2);
         surfaceProvider.onSurfaceRequested(createSurfaceRequest(cameraInfo));
 
         // Create a new surfaceProvider
@@ -840,17 +845,17 @@
         mActivityScenario.onActivity(activity -> activity.setContentView(view));
     }
 
-    private SurfaceRequest createRgb8888SurfaceRequest(CameraInfo cameraInfo) {
+    private SurfaceRequest createRgb8888SurfaceRequest(CameraInfoInternal cameraInfo) {
         return createSurfaceRequest(cameraInfo, true);
     }
 
-    private SurfaceRequest createSurfaceRequest(CameraInfo cameraInfo) {
+    private SurfaceRequest createSurfaceRequest(CameraInfoInternal cameraInfo) {
         return createSurfaceRequest(cameraInfo, false);
     }
 
-    private SurfaceRequest createSurfaceRequest(CameraInfo cameraInfo, boolean isRGBA8888Required) {
-        final FakeCamera fakeCamera = spy(new FakeCamera());
-        when(fakeCamera.getCameraInfo()).thenReturn(cameraInfo);
+    private SurfaceRequest createSurfaceRequest(CameraInfoInternal cameraInfo,
+            boolean isRGBA8888Required) {
+        final FakeCamera fakeCamera = new FakeCamera(/*cameraControl=*/null, cameraInfo);
 
         final SurfaceRequest surfaceRequest = new SurfaceRequest(DEFAULT_SURFACE_SIZE, fakeCamera,
                 isRGBA8888Required);
@@ -858,13 +863,13 @@
         return surfaceRequest;
     }
 
-    private CameraInfo createCameraInfo(String implementationType) {
+    private CameraInfoInternal createCameraInfo(String implementationType) {
         FakeCameraInfoInternal cameraInfoInternal = new FakeCameraInfoInternal();
         cameraInfoInternal.setImplementationType(implementationType);
         return cameraInfoInternal;
     }
 
-    private CameraInfo createCameraInfo(int rotationDegrees, String implementationType,
+    private CameraInfoInternal createCameraInfo(int rotationDegrees, String implementationType,
             @CameraSelector.LensFacing int lensFacing) {
         FakeCameraInfoInternal cameraInfoInternal = new FakeCameraInfoInternal(rotationDegrees,
                 lensFacing);
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java b/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
index fcf66b48..3b44754 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
@@ -61,7 +61,6 @@
 import androidx.camera.core.UseCase;
 import androidx.camera.core.UseCaseGroup;
 import androidx.camera.core.ViewPort;
-import androidx.camera.core.impl.CameraInfoInternal;
 import androidx.camera.core.impl.CameraInternal;
 import androidx.camera.core.impl.ImageOutputConfig;
 import androidx.camera.core.impl.utils.Threads;
@@ -187,7 +186,7 @@
                     : new SurfaceViewImplementation(PreviewView.this, mPreviewTransform);
 
             PreviewStreamStateObserver streamStateObserver =
-                    new PreviewStreamStateObserver((CameraInfoInternal) camera.getCameraInfo(),
+                    new PreviewStreamStateObserver(camera.getCameraInfoInternal(),
                             mPreviewStreamStateLiveData, mImplementation);
             mActiveStreamStateObserver.set(streamStateObserver);
 
@@ -598,7 +597,7 @@
     boolean shouldUseTextureView(@NonNull SurfaceRequest surfaceRequest,
             @NonNull final ImplementationMode implementationMode) {
         // TODO(b/159127402): use TextureView if target rotation is not display rotation.
-        boolean isLegacyDevice = surfaceRequest.getCamera().getCameraInfo()
+        boolean isLegacyDevice = surfaceRequest.getCamera().getCameraInfoInternal()
                 .getImplementationType().equals(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2_LEGACY);
         if (surfaceRequest.isRGBA8888Required() || Build.VERSION.SDK_INT <= 24 || isLegacyDevice) {
             // Force to use TextureView when the device is running android 7.0 and below, legacy
diff --git a/camera/integration-tests/coretestapp/build.gradle b/camera/integration-tests/coretestapp/build.gradle
index 65a474e..1fd48a3 100644
--- a/camera/integration-tests/coretestapp/build.gradle
+++ b/camera/integration-tests/coretestapp/build.gradle
@@ -70,7 +70,8 @@
     implementation(project(":appcompat:appcompat"))
     implementation("androidx.activity:activity:1.2.0")
     implementation("androidx.fragment:fragment:1.3.0")
-    implementation("androidx.concurrent:concurrent-futures:1.0.0")
+    // Needed because AGP enforces same version between main and androidTest classpaths
+    implementation(project(":concurrent:concurrent-futures"))
 
     // Android Support Library
     api(CONSTRAINT_LAYOUT, { transitive = true })
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/ImageUtils.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/ImageUtils.kt
index ab92883..19f829c 100644
--- a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/ImageUtils.kt
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/ImageUtils.kt
@@ -18,6 +18,8 @@
 
 package androidx.camera.integration.antelope
 
+import android.content.ContentResolver
+import android.content.ContentValues
 import android.content.Intent
 import android.graphics.Bitmap
 import android.graphics.ImageFormat
@@ -26,6 +28,7 @@
 import android.net.Uri
 import android.os.Build
 import android.os.Environment
+import android.provider.MediaStore
 import android.widget.Toast
 import androidx.annotation.RequiresApi
 import androidx.camera.core.ImageCapture
@@ -183,6 +186,18 @@
  * Actually write a byteArray file to disk. Assume the file is a jpg and use that extension
  */
 fun writeFile(activity: MainActivity, bytes: ByteArray) {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+        writeFileAfterQ(activity, bytes)
+    } else {
+        writeFileBeforeQ(activity, bytes)
+    }
+}
+
+/**
+ * Original writeFile implementation. It is workable on Pie and Pei lower for
+ * Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
+ */
+fun writeFileBeforeQ(activity: MainActivity, bytes: ByteArray) {
     val jpgFile = File(
         Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
         File.separatorChar + PHOTOS_DIR + File.separatorChar +
@@ -240,6 +255,54 @@
 }
 
 /**
+ * After Q, change to use MediaStore to access the shared media files.
+ * https://developer.android.com/training/data-storage/shared
+ */
+fun writeFileAfterQ(activity: MainActivity, bytes: ByteArray) {
+    var imageUri: Uri?
+    val resolver: ContentResolver = activity.contentResolver
+
+    val relativeLocation = Environment.DIRECTORY_DCIM + File.separatorChar + PHOTOS_DIR
+    val contentValues = ContentValues().apply {
+        put(MediaStore.MediaColumns.DISPLAY_NAME, generateTimestamp().toString() + ".jpg")
+        put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
+        put(MediaStore.MediaColumns.RELATIVE_PATH, relativeLocation)
+    }
+
+    imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
+    if (imageUri != null) {
+        val output = activity.contentResolver.openOutputStream(imageUri)
+        try {
+            output?.write(bytes)
+        } catch (e: IOException) {
+            e.printStackTrace()
+        } finally {
+            if (null != output) {
+                try {
+                    output.close()
+                } catch (e: IOException) {
+                    e.printStackTrace()
+                }
+            }
+        }
+        logd("writeFile: Completed.")
+        if (PrefHelper.getAutoDelete(activity)) {
+            val result = resolver.delete(imageUri, null, null)
+            if (result > 0) {
+                logd("Delete image $imageUri completed.")
+            }
+        }
+    } else {
+        activity.runOnUiThread {
+            Toast.makeText(
+                activity, "Image file creation failed.",
+                Toast.LENGTH_SHORT
+            ).show()
+        }
+    }
+}
+
+/**
  * Delete all the photos generated by testing from the default Antelope PHOTOS_DIR
  */
 fun deleteTestPhotos(activity: MainActivity) {
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MainActivity.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MainActivity.kt
index a5124a2..2027e63 100644
--- a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MainActivity.kt
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/MainActivity.kt
@@ -32,7 +32,6 @@
 import android.view.View
 import android.view.WindowManager
 import android.widget.Toast
-import androidx.activity.result.launch
 import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
 import androidx.appcompat.app.AppCompatActivity
 import androidx.camera.integration.antelope.cameracontrollers.camera2Abort
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TestResults.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TestResults.kt
index 79fe505..5dedacf 100644
--- a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TestResults.kt
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TestResults.kt
@@ -18,17 +18,23 @@
 
 package androidx.camera.integration.antelope
 
+import android.content.ContentResolver
+import android.content.ContentValues
 import android.content.Intent
 import android.net.Uri
+import android.os.Build
 import android.os.Environment
+import android.provider.MediaStore
 import android.widget.Toast
+import androidx.camera.integration.antelope.MainActivity.Companion.LOG_DIR
+import androidx.camera.integration.antelope.MainActivity.Companion.logd
 import com.google.common.math.Quantiles
 import com.google.common.math.Stats
-import androidx.camera.integration.antelope.MainActivity.Companion.logd
 import java.io.BufferedWriter
 import java.io.File
 import java.io.FileOutputStream
 import java.io.IOException
+import java.io.OutputStream
 import java.io.OutputStreamWriter
 import java.text.SimpleDateFormat
 import java.util.Calendar
@@ -275,13 +281,24 @@
  * @param csv The comma-based csv string
  */
 fun writeCSV(activity: MainActivity, filePrefix: String, csv: String) {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+        writeCSVAfterQ(activity, filePrefix, csv)
+    } else {
+        writeCSVBeforeQ(activity, filePrefix, csv)
+    }
+}
 
+/**
+* Original writeFile implementation. It is workable on Pie and Pei lower for
+* Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)
+*/
+fun writeCSVBeforeQ(activity: MainActivity, prefix: String, csv: String) {
     val csvFile = File(
         Environment.getExternalStoragePublicDirectory(
             Environment.DIRECTORY_DOCUMENTS
         ),
         File.separatorChar + MainActivity.LOG_DIR + File.separatorChar +
-            filePrefix + "_" + generateCSVTimestamp() + ".csv"
+            prefix + "_" + generateCSVTimestamp() + ".csv"
     )
 
     val csvDir = File(
@@ -345,18 +362,66 @@
         scannerIntent.data = Uri.fromFile(csvFile)
         activity.sendBroadcast(scannerIntent)
     } catch (e: IOException) {
-        logd("IOException vail on CSV write: " + e.printStackTrace())
+        logd("IOException Fail on CSV write: " + e.printStackTrace())
     } finally {
         try {
             output.close()
         } catch (e: IOException) {
-            logd("IOException vail on CSV close: " + e.printStackTrace())
+            logd("IOException Fail on CSV close: " + e.printStackTrace())
             e.printStackTrace()
         }
     }
 }
 
 /**
+ * After Q, change to use MediaStore to access the shared media files.
+ * https://developer.android.com/training/data-storage/shared
+ *
+ * @param activity The main activity
+ * @param prefix The prefix for the .csv file
+ * @param csv The comma-based csv string
+ */
+fun writeCSVAfterQ(activity: MainActivity, prefix: String, csv: String) {
+    var output: OutputStream?
+    var csvUri: Uri?
+    val resolver: ContentResolver = activity.contentResolver
+
+    val relativePath = Environment.DIRECTORY_DOCUMENTS + File.separatorChar + LOG_DIR
+    val contentValues = ContentValues().apply {
+        put(MediaStore.MediaColumns.DISPLAY_NAME, prefix + "_" + generateCSVTimestamp() + ".csv")
+        put(MediaStore.MediaColumns.MIME_TYPE, "text/comma-separated-values")
+        put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath)
+    }
+
+    csvUri = resolver.insert(MediaStore.Files.getContentUri("external"), contentValues)
+    if (csvUri != null) {
+        lateinit var bufferWriter: BufferedWriter
+        try {
+            output = activity.contentResolver.openOutputStream(csvUri)
+            bufferWriter = BufferedWriter(OutputStreamWriter(output))
+            bufferWriter.write(csv)
+            logd("CSV write completed successfully.")
+        } catch (e: IOException) {
+            logd("IOException Fail on CSV write: " + e.printStackTrace())
+        } finally {
+            try {
+                bufferWriter.close()
+            } catch (e: IOException) {
+                logd("IOException Fail on CSV close: " + e.printStackTrace())
+                e.printStackTrace()
+            }
+        }
+    } else {
+        activity.runOnUiThread {
+            Toast.makeText(
+                activity, "CSV log file creation failed.",
+                Toast.LENGTH_SHORT
+            ).show()
+        }
+    }
+}
+
+/**
  * Delete all Antelope .csv files in the documents directory
  */
 fun deleteCSVFiles(activity: MainActivity) {
@@ -442,7 +507,7 @@
 ): String {
     var output = ""
 
-    // If every result is false, don't output this line at all
+// If every result is false, don't output this line at all
     if (!results.isEmpty() && results.contains(true)) {
         output += name + ": "
         for ((index, result) in results.withIndex()) {
diff --git a/camera/integration-tests/viewtestapp/build.gradle b/camera/integration-tests/viewtestapp/build.gradle
index 0a85291..ca49b03 100644
--- a/camera/integration-tests/viewtestapp/build.gradle
+++ b/camera/integration-tests/viewtestapp/build.gradle
@@ -72,14 +72,17 @@
     androidTestImplementation(ANDROIDX_TEST_RULES)
     androidTestImplementation(ANDROIDX_TEST_UIAUTOMATOR)
     androidTestImplementation(ESPRESSO_CORE)
-    androidTestImplementation(project(":camera:camera-testing"))
     androidTestImplementation(project(":lifecycle:lifecycle-runtime-testing"))
     androidTestImplementation(project(":lifecycle:lifecycle-runtime"))
     androidTestImplementation("androidx.lifecycle:lifecycle-livedata-ktx:2.2.0")
     androidTestImplementation(TRUTH)
     debugImplementation(ANDROIDX_TEST_CORE)
     debugImplementation("androidx.fragment:fragment-testing:1.2.3")
-    // Testing resource dependency for manifest
-    debugImplementation(project(":camera:camera-testing"))
+    // camera-testing added as 'implementation' dependency to include camera-testing activity in APK
+    debugImplementation(project(":camera:camera-testing")) {
+        // Ensure camera-testing does not pull in camera-core project dependency which will
+        // override pinned dependency.
+        exclude(group:"androidx.camera", module:"camera-core")
+    }
 }
 
diff --git a/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppActivity.java b/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppActivity.java
index 9831385..518c947 100644
--- a/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppActivity.java
+++ b/car/app/app-activity/src/main/java/androidx/car/app/activity/CarAppActivity.java
@@ -53,18 +53,48 @@
 import java.util.List;
 
 /**
- * The class representing all the car app activities. This class is responsible for binding to the
- * host and rendering the content given by the car app service.
+ * The class representing a car app activity.
  *
- * <p> The apps that wish to show their content in a {@link CarAppActivity}, should define an
- * activity-alias for the {@link  CarAppActivity} and provide the car app service associated with
- * the activity using a metadata tag.
+ * <p>This class is responsible for binding to the host and rendering the content given by a
+ * {@link androidx.car.app.CarAppService}.
+ *
+ * <p>Usage of {@link CarAppActivity} is only required for applications targeting Automotive OS.
+ *
+ * <h4>Activity Declaration</h4>
+ * The app must declare an {@code activity-alias} for a {@link CarAppActivity} providing its
+ * associated {@link androidx.car.app.CarAppService} as meta-data. For example:
+ *
+ * <pre>{@code
+ * <activity-alias
+ *   android:enabled="true"
+ *   android:exported="true"
+ *   android:label="@string/your_app_label"
+ *   android:name=".YourActivityAliasName"
+ *   android:targetActivity="androidx.car.app.activity.CarAppActivity" >
+ *   <intent-filter>
+ *     <action android:name="android.intent.action.MAIN" />
+ *     <category android:name="android.intent.category.LAUNCHER" />
+ *   </intent-filter>
+ *   <meta-data
+ *     android:name="androidx.car.app.CAR_APP_SERVICE"
+ *     android:value=".YourCarAppService" />
+ *   <meta-data android:name="distractionOptimized" android:value="true"/>
+ * </activity-alias>
+ * }</pre>
+ *
+ * <p>See {@link androidx.car.app.CarAppService} for how to declare your app's car app service in
+ * the manifest.
+ *
+ * <p>Note the name of the alias should be unique and resemble a fully qualified class name, but
+ * unlike the name of the target activity, the alias name is arbitrary; it does not refer to an
+ * actual class.
  */
-//TODO(b/179146927) update javadoc
+// TODO(b/179225768): Remove distractionOptimized from the javadoc above if we can make that
+// implicit for car apps.
 @SuppressLint({"ForbiddenSuperClass"})
 public final class CarAppActivity extends Activity {
     @VisibleForTesting
-    static final String SERVICE_METADATA_KEY = "car-app-service";
+    static final String SERVICE_METADATA_KEY = "androidx.car.app.CAR_APP_SERVICE";
     private static final String TAG = "CarAppActivity";
 
     // TODO(b/177448399): Update after service intent action is added to car-lib.
diff --git a/car/app/app-samples/helloworld/build.gradle b/car/app/app-samples/helloworld/build.gradle
index fa4fdb8..a6b8625 100644
--- a/car/app/app-samples/helloworld/build.gradle
+++ b/car/app/app-samples/helloworld/build.gradle
@@ -1,3 +1,8 @@
+import static androidx.build.dependencies.DependenciesKt.ANDROIDX_TEST_CORE
+import static androidx.build.dependencies.DependenciesKt.JUNIT
+import static androidx.build.dependencies.DependenciesKt.ROBOLECTRIC
+import static androidx.build.dependencies.DependenciesKt.TRUTH
+
 /*
  * Copyright (C) 2021 The Android Open Source Project
  *
@@ -26,11 +31,25 @@
         versionCode 1
         versionName "1.0"
     }
+
+    buildTypes {
+        release {
+            // Enables code shrinking, obfuscation, and optimization.
+            minifyEnabled true
+            proguardFiles getDefaultProguardFile(
+                    'proguard-android-optimize.txt'),
+                    'proguard-rules.pro'
+        }
+    }
 }
 
 dependencies {
     implementation(project(":car:app:app"))
 
-    implementation("androidx.lifecycle:lifecycle-common-java8:2.2.0")
+    testImplementation(ROBOLECTRIC)
+    testImplementation(ANDROIDX_TEST_CORE)
+    testImplementation(JUNIT)
+    testImplementation(TRUTH)
+    testImplementation(project(":car:app:app-testing"))
 }
 
diff --git a/car/app/app-samples/helloworld/github_build.gradle b/car/app/app-samples/helloworld/github_build.gradle
index ff55b54..b6e2004 100644
--- a/car/app/app-samples/helloworld/github_build.gradle
+++ b/car/app/app-samples/helloworld/github_build.gradle
@@ -35,13 +35,6 @@
 
 dependencies {
     implementation "androidx.car.app:app:1.0.0-beta01"
-
-    // TODO transitive dependencies for the library. Remove once they can be fetched from the pom file.
-    implementation "androidx.activity:activity:1.1.0"
-    implementation "androidx.annotation:annotation:1.1.0"
-    implementation "androidx.core:core:1.3.0"
-    implementation "androidx.lifecycle:lifecycle-viewmodel:2.2.0"
-    implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
 }
 
 
diff --git a/car/app/app-samples/helloworld/src/test/java/androidx/car/app/samples/helloworld/HelloWorldScreenTest.java b/car/app/app-samples/helloworld/src/test/java/androidx/car/app/samples/helloworld/HelloWorldScreenTest.java
new file mode 100644
index 0000000..0d96725
--- /dev/null
+++ b/car/app/app-samples/helloworld/src/test/java/androidx/car/app/samples/helloworld/HelloWorldScreenTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.car.app.samples.helloworld;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.car.app.model.PaneTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.testing.TestCarContext;
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+import java.util.List;
+
+/**
+ * A sample test on {@link HelloWorldScreen}.
+ *
+ * <p>Demonstrating the usage of {@link TestCarContext} and validating that the returned
+ * {@link androidx.car.app.model.Template} has the expected contents.
+ */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class HelloWorldScreenTest {
+    private final TestCarContext mTestCarContext =
+            TestCarContext.createCarContext(ApplicationProvider.getApplicationContext());
+
+    @Test
+    public void getTemplate_containsExpectedRow() {
+        HelloWorldScreen screen = new HelloWorldScreen(mTestCarContext);
+        PaneTemplate template = (PaneTemplate) screen.onGetTemplate();
+
+        List<Row> rows = template.getPane().getRows();
+        assertThat(rows).hasSize(1);
+        assertThat(rows.get(0)).isEqualTo(new Row.Builder().setTitle("Hello AndroidX!").build());
+    }
+}
diff --git a/car/app/app-samples/helloworld/src/test/java/androidx/car/app/samples/helloworld/HelloWorldSessionTest.java b/car/app/app-samples/helloworld/src/test/java/androidx/car/app/samples/helloworld/HelloWorldSessionTest.java
new file mode 100644
index 0000000..1a126b0
--- /dev/null
+++ b/car/app/app-samples/helloworld/src/test/java/androidx/car/app/samples/helloworld/HelloWorldSessionTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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.car.app.samples.helloworld;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Intent;
+
+import androidx.car.app.Screen;
+import androidx.car.app.Session;
+import androidx.car.app.testing.SessionController;
+import androidx.car.app.testing.TestCarContext;
+import androidx.car.app.testing.TestScreenManager;
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+/**
+ * A sample test on the session instance from {@link HelloWorldService}.
+ *
+ * <p>Demonstrating the usage of {@link SessionController} and validating that the session is
+ * pushing the expected screen when created.
+ */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class HelloWorldSessionTest {
+    private final TestCarContext mTestCarContext =
+            TestCarContext.createCarContext(ApplicationProvider.getApplicationContext());
+
+    @Test
+    public void onCreateScreen_returnsExpectedScreen() {
+        HelloWorldService service = Robolectric.setupService(HelloWorldService.class);
+        Session session = service.onCreateSession();
+        SessionController controller = SessionController.of(session, mTestCarContext);
+
+        controller.create(new Intent().setComponent(
+                new ComponentName(mTestCarContext, HelloWorldService.class)));
+        Screen screenCreated =
+                mTestCarContext.getCarService(TestScreenManager.class).getScreensPushed().get(0);
+        assertThat(screenCreated).isInstanceOf(HelloWorldScreen.class);
+    }
+
+}
diff --git a/car/app/app-samples/helloworld/src/test/resources/robolectric.properties b/car/app/app-samples/helloworld/src/test/resources/robolectric.properties
new file mode 100644
index 0000000..89a6c8b
--- /dev/null
+++ b/car/app/app-samples/helloworld/src/test/resources/robolectric.properties
@@ -0,0 +1 @@
+sdk=28
\ No newline at end of file
diff --git a/car/app/app-samples/navigation/build.gradle b/car/app/app-samples/navigation/build.gradle
index 81f3c50..8365d1e 100644
--- a/car/app/app-samples/navigation/build.gradle
+++ b/car/app/app-samples/navigation/build.gradle
@@ -27,11 +27,20 @@
         versionCode 1
         versionName "1.0"
     }
+
+    buildTypes {
+        release {
+            // Enables code shrinking, obfuscation, and optimization.
+            minifyEnabled true
+            proguardFiles getDefaultProguardFile(
+                    'proguard-android-optimize.txt'),
+                    'proguard-rules.pro'
+        }
+    }
 }
 
 dependencies {
     implementation(project(":car:app:app"))
 
     implementation("androidx.core:core:1.5.0-alpha01")
-    implementation("androidx.lifecycle:lifecycle-common-java8:2.2.0")
 }
diff --git a/car/app/app-samples/navigation/github_build.gradle b/car/app/app-samples/navigation/github_build.gradle
index 92fb405..41ece82 100644
--- a/car/app/app-samples/navigation/github_build.gradle
+++ b/car/app/app-samples/navigation/github_build.gradle
@@ -38,10 +38,4 @@
     implementation "androidx.core:core:1.5.0-alpha01"
 
     implementation "androidx.car.app:app:1.0.0-beta01"
-
-    // TODO transitive dependencies for the library. Remove once they can be fetched from the pom file.
-    implementation "androidx.activity:activity:1.1.0"
-    implementation "androidx.annotation:annotation:1.1.0"
-    implementation "androidx.lifecycle:lifecycle-viewmodel:2.2.0"
-    implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
 }
diff --git a/car/app/app-samples/places/build.gradle b/car/app/app-samples/places/build.gradle
index 8ecb4fa..01222f5 100644
--- a/car/app/app-samples/places/build.gradle
+++ b/car/app/app-samples/places/build.gradle
@@ -28,6 +28,16 @@
         versionCode 1
         versionName "1.0"
     }
+
+    buildTypes {
+        release {
+            // Enables code shrinking, obfuscation, and optimization.
+            minifyEnabled true
+            proguardFiles getDefaultProguardFile(
+                    'proguard-android-optimize.txt'),
+                    'proguard-rules.pro'
+        }
+    }
 }
 
 dependencies {
@@ -36,5 +46,4 @@
     implementation(GUAVA_ANDROID)
 
     implementation("androidx.core:core:1.5.0-alpha01")
-    implementation("androidx.lifecycle:lifecycle-common-java8:2.2.0")
 }
diff --git a/car/app/app-samples/places/github_build.gradle b/car/app/app-samples/places/github_build.gradle
index db897f2..681d3c18 100644
--- a/car/app/app-samples/places/github_build.gradle
+++ b/car/app/app-samples/places/github_build.gradle
@@ -37,11 +37,4 @@
     implementation 'com.google.guava:guava:28.1-jre'
 
     implementation "androidx.car.app:app:1.0.0-beta01"
-
-    // TODO transitive dependencies for the library. Remove once they can be fetched from the pom file.
-    implementation "androidx.activity:activity:1.1.0"
-    implementation "androidx.annotation:annotation:1.1.0"
-    implementation "androidx.core:core:1.3.0"
-    implementation "androidx.lifecycle:lifecycle-viewmodel:2.2.0"
-    implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
 }
\ No newline at end of file
diff --git a/car/app/app-samples/showcase/build.gradle b/car/app/app-samples/showcase/build.gradle
index d47cb10..661adad 100644
--- a/car/app/app-samples/showcase/build.gradle
+++ b/car/app/app-samples/showcase/build.gradle
@@ -27,11 +27,20 @@
         versionCode 1
         versionName "1.0"
     }
+
+    buildTypes {
+        release {
+            // Enables code shrinking, obfuscation, and optimization.
+            minifyEnabled true
+            proguardFiles getDefaultProguardFile(
+                    'proguard-android-optimize.txt'),
+                    'proguard-rules.pro'
+        }
+    }
 }
 
 dependencies {
     implementation(project(":car:app:app"))
 
     implementation("androidx.core:core:1.5.0-alpha01")
-    implementation("androidx.lifecycle:lifecycle-common-java8:2.2.0")
 }
diff --git a/car/app/app-samples/showcase/github_build.gradle b/car/app/app-samples/showcase/github_build.gradle
index 64c1b4d..68f2c69 100644
--- a/car/app/app-samples/showcase/github_build.gradle
+++ b/car/app/app-samples/showcase/github_build.gradle
@@ -39,10 +39,4 @@
     implementation "androidx.core:core:1.5.0-alpha01"
 
     implementation "androidx.car.app:app:1.0.0-beta01"
-
-    // TODO transitive dependencies for the library. Remove once they can be fetched from the pom file.
-    implementation "androidx.activity:activity:1.1.0"
-    implementation "androidx.annotation:annotation:1.1.0"
-    implementation "androidx.lifecycle:lifecycle-viewmodel:2.2.0"
-    implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
 }
diff --git a/car/app/app-samples/showcase/src/main/AndroidManifest.xml b/car/app/app-samples/showcase/src/main/AndroidManifest.xml
index 688b309..50f778d 100644
--- a/car/app/app-samples/showcase/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/showcase/src/main/AndroidManifest.xml
@@ -50,7 +50,7 @@
         android:name=".ShowcaseService"
         android:exported="true">
       <intent-filter>
-        <action android:name="androidx.car.app.CarAppService" />
+        <action android:name="androidx.car.app.CarAppService"/>
         <category android:name="androidx.car.app.category.NAVIGATION"/>
       </intent-filter>
     </service>
diff --git a/car/app/app-testing/api/current.txt b/car/app/app-testing/api/current.txt
index 52b6e21..b4d3dba 100644
--- a/car/app/app-testing/api/current.txt
+++ b/car/app/app-testing/api/current.txt
@@ -20,7 +20,7 @@
   }
 
   public class SessionController {
-    method public androidx.car.app.testing.SessionController create();
+    method public androidx.car.app.testing.SessionController create(android.content.Intent);
     method public androidx.car.app.testing.SessionController destroy();
     method public androidx.car.app.Session get();
     method public static androidx.car.app.testing.SessionController of(androidx.car.app.Session, androidx.car.app.testing.TestCarContext);
diff --git a/car/app/app-testing/api/public_plus_experimental_current.txt b/car/app/app-testing/api/public_plus_experimental_current.txt
index 52b6e21..b4d3dba 100644
--- a/car/app/app-testing/api/public_plus_experimental_current.txt
+++ b/car/app/app-testing/api/public_plus_experimental_current.txt
@@ -20,7 +20,7 @@
   }
 
   public class SessionController {
-    method public androidx.car.app.testing.SessionController create();
+    method public androidx.car.app.testing.SessionController create(android.content.Intent);
     method public androidx.car.app.testing.SessionController destroy();
     method public androidx.car.app.Session get();
     method public static androidx.car.app.testing.SessionController of(androidx.car.app.Session, androidx.car.app.testing.TestCarContext);
diff --git a/car/app/app-testing/api/restricted_current.txt b/car/app/app-testing/api/restricted_current.txt
index 52b6e21..b4d3dba 100644
--- a/car/app/app-testing/api/restricted_current.txt
+++ b/car/app/app-testing/api/restricted_current.txt
@@ -20,7 +20,7 @@
   }
 
   public class SessionController {
-    method public androidx.car.app.testing.SessionController create();
+    method public androidx.car.app.testing.SessionController create(android.content.Intent);
     method public androidx.car.app.testing.SessionController destroy();
     method public androidx.car.app.Session get();
     method public static androidx.car.app.testing.SessionController of(androidx.car.app.Session, androidx.car.app.testing.TestCarContext);
diff --git a/car/app/app-testing/src/main/java/androidx/car/app/testing/SessionController.java b/car/app/app-testing/src/main/java/androidx/car/app/testing/SessionController.java
index 2854441..153290f 100644
--- a/car/app/app-testing/src/main/java/androidx/car/app/testing/SessionController.java
+++ b/car/app/app-testing/src/main/java/androidx/car/app/testing/SessionController.java
@@ -18,8 +18,11 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.content.Intent;
+
 import androidx.annotation.NonNull;
 import androidx.car.app.Session;
+import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.Lifecycle.Event;
 import androidx.lifecycle.LifecycleRegistry;
 
@@ -53,14 +56,28 @@
     }
 
     /**
-     * Creates the {@link Session} that is being controlled.
+     * Creates the {@link Session} that is being controlled with the given {@code intent}.
+     *
+     * <p>If this is the first time this is called on the {@link Session}, this would trigger
+     * {@link Session#onCreateScreen(Intent)} and transition the lifecycle to the
+     * {@link Lifecycle.State#CREATED} state. Otherwise, this will trigger
+     * {@link Session#onNewIntent(Intent)}.
      *
      * @see Session#getLifecycle
      */
     @NonNull
-    public SessionController create() {
+    public SessionController create(@NonNull Intent intent) {
         LifecycleRegistry registry = (LifecycleRegistry) mSession.getLifecycle();
-        registry.handleLifecycleEvent(Event.ON_CREATE);
+        Lifecycle.State state = registry.getCurrentState();
+        TestScreenManager screenManager = mTestCarContext.getCarService(TestScreenManager.class);
+
+        int screenStackSize = screenManager.getScreensPushed().size();
+        if (!state.isAtLeast(Lifecycle.State.CREATED) || screenStackSize < 1) {
+            registry.handleLifecycleEvent(Event.ON_CREATE);
+            screenManager.push(mSession.onCreateScreen(intent));
+        } else {
+            mSession.onNewIntent(intent);
+        }
 
         return this;
     }
diff --git a/car/app/app-testing/src/test/java/androidx/car/app/testing/SessionControllerTest.java b/car/app/app-testing/src/test/java/androidx/car/app/testing/SessionControllerTest.java
index 4faf72b..e2df7e1 100644
--- a/car/app/app-testing/src/test/java/androidx/car/app/testing/SessionControllerTest.java
+++ b/car/app/app-testing/src/test/java/androidx/car/app/testing/SessionControllerTest.java
@@ -19,6 +19,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.verify;
 
+import android.content.ComponentName;
 import android.content.Intent;
 
 import androidx.annotation.NonNull;
@@ -47,12 +48,15 @@
 
     private SessionController mSessionController;
     private TestCarContext mCarContext;
+    private Intent mIntent;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mCarContext = TestCarContext.createCarContext(
                 ApplicationProvider.getApplicationContext());
+        mIntent = new Intent().setComponent(new ComponentName(mCarContext,
+                this.getClass()));
 
         Session session = new Session() {
             @NonNull
@@ -69,14 +73,14 @@
 
     @Test
     public void create() {
-        mSessionController.create();
+        mSessionController.create(mIntent);
 
         verify(mMockObserver).onCreate(any());
     }
 
     @Test
     public void start() {
-        mSessionController.create().start();
+        mSessionController.create(mIntent).start();
 
         verify(mMockObserver).onCreate(any());
         verify(mMockObserver).onStart(any());
@@ -84,7 +88,7 @@
 
     @Test
     public void resume() {
-        mSessionController.create().resume();
+        mSessionController.create(mIntent).resume();
 
         verify(mMockObserver).onCreate(any());
         verify(mMockObserver).onStart(any());
@@ -93,7 +97,7 @@
 
     @Test
     public void pause() {
-        mSessionController.create().resume().pause();
+        mSessionController.create(mIntent).resume().pause();
 
         verify(mMockObserver).onCreate(any());
         verify(mMockObserver).onStart(any());
@@ -103,7 +107,7 @@
 
     @Test
     public void stop() {
-        mSessionController.create().resume().stop();
+        mSessionController.create(mIntent).resume().stop();
 
         verify(mMockObserver).onCreate(any());
         verify(mMockObserver).onStart(any());
@@ -114,7 +118,7 @@
 
     @Test
     public void destroy() {
-        mSessionController.create().resume().destroy();
+        mSessionController.create(mIntent).resume().destroy();
 
         verify(mMockObserver).onCreate(any());
         verify(mMockObserver).onStart(any());
diff --git a/car/app/app/api/public_plus_experimental_1.0.0-beta02.txt b/car/app/app/api/public_plus_experimental_1.0.0-beta02.txt
index 7ef00c9..228fc69 100644
--- a/car/app/app/api/public_plus_experimental_1.0.0-beta02.txt
+++ b/car/app/app/api/public_plus_experimental_1.0.0-beta02.txt
@@ -190,6 +190,7 @@
     method public androidx.car.app.model.Action.Builder setIcon(androidx.car.app.model.CarIcon);
     method public androidx.car.app.model.Action.Builder setOnClickListener(androidx.car.app.model.OnClickListener);
     method public androidx.car.app.model.Action.Builder setTitle(CharSequence);
+    method @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.Action.Builder setTitle(androidx.car.app.model.CarText);
   }
 
   public final class ActionStrip {
@@ -271,11 +272,18 @@
 
   public final class CarText {
     method public static androidx.car.app.model.CarText create(CharSequence);
+    method @androidx.car.app.annotations.ExperimentalCarApi public java.util.List<java.lang.CharSequence!> getVariants();
     method public boolean isEmpty();
     method public static boolean isNullOrEmpty(androidx.car.app.model.CarText?);
     method public CharSequence toCharSequence();
   }
 
+  @androidx.car.app.annotations.ExperimentalCarApi public static final class CarText.Builder {
+    ctor public CarText.Builder(CharSequence);
+    method public androidx.car.app.model.CarText.Builder addVariant(CharSequence);
+    method public androidx.car.app.model.CarText build();
+  }
+
   @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(2) public final class ClickableSpan extends androidx.car.app.model.CarSpan {
     method public static androidx.car.app.model.ClickableSpan create(androidx.car.app.model.OnClickListener);
     method public androidx.car.app.model.OnClickDelegate getOnClickDelegate();
@@ -338,7 +346,9 @@
     method public androidx.car.app.model.GridItem.Builder setLoading(boolean);
     method public androidx.car.app.model.GridItem.Builder setOnClickListener(androidx.car.app.model.OnClickListener);
     method public androidx.car.app.model.GridItem.Builder setText(CharSequence);
+    method @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridItem.Builder setText(androidx.car.app.model.CarText);
     method public androidx.car.app.model.GridItem.Builder setTitle(CharSequence);
+    method @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridItem.Builder setTitle(androidx.car.app.model.CarText);
   }
 
   public final class GridTemplate implements androidx.car.app.model.Template {
@@ -419,6 +429,7 @@
 
   public static final class MessageTemplate.Builder {
     ctor public MessageTemplate.Builder(CharSequence);
+    ctor @androidx.car.app.annotations.ExperimentalCarApi public MessageTemplate.Builder(androidx.car.app.model.CarText);
     method public androidx.car.app.model.MessageTemplate.Builder addAction(androidx.car.app.model.Action);
     method public androidx.car.app.model.MessageTemplate build();
     method public androidx.car.app.model.MessageTemplate.Builder setDebugMessage(Throwable);
@@ -565,6 +576,7 @@
   public static final class Row.Builder {
     ctor public Row.Builder();
     method public androidx.car.app.model.Row.Builder addText(CharSequence);
+    method @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.Row.Builder addText(androidx.car.app.model.CarText);
     method public androidx.car.app.model.Row build();
     method public androidx.car.app.model.Row.Builder setBrowsable(boolean);
     method public androidx.car.app.model.Row.Builder setImage(androidx.car.app.model.CarIcon);
@@ -572,6 +584,7 @@
     method public androidx.car.app.model.Row.Builder setMetadata(androidx.car.app.model.Metadata);
     method public androidx.car.app.model.Row.Builder setOnClickListener(androidx.car.app.model.OnClickListener);
     method public androidx.car.app.model.Row.Builder setTitle(CharSequence);
+    method @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.Row.Builder setTitle(androidx.car.app.model.CarText);
     method public androidx.car.app.model.Row.Builder setToggle(androidx.car.app.model.Toggle);
   }
 
@@ -655,6 +668,87 @@
 
 }
 
+package androidx.car.app.model.signin {
+
+  @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(2) public final class InputSignInMethod implements androidx.car.app.model.signin.SignInTemplate.SignInMethod {
+    method public androidx.car.app.model.CarText? getDefaultValue();
+    method public int getInputType();
+    method public int getKeyboardType();
+    method public androidx.car.app.model.CarText? getMessage();
+    method public androidx.car.app.model.signin.OnInputCompletedDelegate getOnInputCompletedDelegate();
+    method public androidx.car.app.model.CarText? getPrompt();
+    method public boolean isShowKeyboardByDefault();
+    field public static final int INPUT_TYPE_DEFAULT = 1; // 0x1
+    field public static final int INPUT_TYPE_PASSWORD = 2; // 0x2
+    field public static final int KEYBOARD_DEFAULT = 1; // 0x1
+    field public static final int KEYBOARD_EMAIL = 2; // 0x2
+    field public static final int KEYBOARD_NUMBER = 4; // 0x4
+    field public static final int KEYBOARD_PHONE = 3; // 0x3
+  }
+
+  public static final class InputSignInMethod.Builder {
+    ctor public InputSignInMethod.Builder(androidx.car.app.model.signin.InputSignInMethod.OnInputCompletedListener);
+    method public androidx.car.app.model.signin.InputSignInMethod build();
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setDefaultValue(String);
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setInputType(int);
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setKeyboardType(int);
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setMessage(CharSequence);
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setPrompt(CharSequence);
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setShowKeyboardByDefault(boolean);
+  }
+
+  public static interface InputSignInMethod.OnInputCompletedListener {
+    method public void onInputCompleted(String);
+  }
+
+  @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(2) public interface OnInputCompletedDelegate {
+    method public void sendInputCompleted(String, androidx.car.app.OnDoneCallback);
+  }
+
+  @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(2) public final class PinSignInMethod implements androidx.car.app.model.signin.SignInTemplate.SignInMethod {
+    method public String getPin();
+  }
+
+  public static final class PinSignInMethod.Builder {
+    ctor public PinSignInMethod.Builder(String);
+    method public androidx.car.app.model.signin.PinSignInMethod build();
+  }
+
+  @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(2) public final class ProviderSignInMethod implements androidx.car.app.model.signin.SignInTemplate.SignInMethod {
+    method public androidx.car.app.model.Action getAction();
+  }
+
+  public static final class ProviderSignInMethod.Builder {
+    ctor public ProviderSignInMethod.Builder(androidx.car.app.model.Action);
+    method public androidx.car.app.model.signin.ProviderSignInMethod build();
+  }
+
+  @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(2) public final class SignInTemplate implements androidx.car.app.model.Template {
+    method public androidx.car.app.model.ActionStrip? getActionStrip();
+    method public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.model.CarText? getAdditionalText();
+    method public androidx.car.app.model.Action? getHeaderAction();
+    method public androidx.car.app.model.CarText? getInstructions();
+    method public androidx.car.app.model.signin.SignInTemplate.SignInMethod getSignInMethod();
+    method public androidx.car.app.model.CarText? getTitle();
+  }
+
+  public static final class SignInTemplate.Builder {
+    ctor public SignInTemplate.Builder(androidx.car.app.model.signin.SignInTemplate.SignInMethod);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.signin.SignInTemplate build();
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setAdditionalText(CharSequence);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setInstructions(CharSequence);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setTitle(CharSequence);
+  }
+
+  public static interface SignInTemplate.SignInMethod {
+  }
+
+}
+
 package androidx.car.app.navigation {
 
   public class NavigationManager {
@@ -787,9 +881,11 @@
 
   public static final class MessageInfo.Builder {
     ctor public MessageInfo.Builder(CharSequence);
+    ctor @androidx.car.app.annotations.ExperimentalCarApi public MessageInfo.Builder(androidx.car.app.model.CarText);
     method public androidx.car.app.navigation.model.MessageInfo build();
     method public androidx.car.app.navigation.model.MessageInfo.Builder setImage(androidx.car.app.model.CarIcon);
     method public androidx.car.app.navigation.model.MessageInfo.Builder setText(CharSequence);
+    method @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.navigation.model.MessageInfo.Builder setText(androidx.car.app.model.CarText);
     method public androidx.car.app.navigation.model.MessageInfo.Builder setTitle(CharSequence);
   }
 
@@ -877,6 +973,7 @@
 
   public static final class Step.Builder {
     ctor public Step.Builder(CharSequence);
+    ctor @androidx.car.app.annotations.ExperimentalCarApi public Step.Builder(androidx.car.app.model.CarText);
     method public androidx.car.app.navigation.model.Step.Builder addLane(androidx.car.app.navigation.model.Lane);
     method public androidx.car.app.navigation.model.Step build();
     method public androidx.car.app.navigation.model.Step.Builder setCue(CharSequence);
diff --git a/car/app/app/api/public_plus_experimental_current.txt b/car/app/app/api/public_plus_experimental_current.txt
index 7ef00c9..228fc69 100644
--- a/car/app/app/api/public_plus_experimental_current.txt
+++ b/car/app/app/api/public_plus_experimental_current.txt
@@ -190,6 +190,7 @@
     method public androidx.car.app.model.Action.Builder setIcon(androidx.car.app.model.CarIcon);
     method public androidx.car.app.model.Action.Builder setOnClickListener(androidx.car.app.model.OnClickListener);
     method public androidx.car.app.model.Action.Builder setTitle(CharSequence);
+    method @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.Action.Builder setTitle(androidx.car.app.model.CarText);
   }
 
   public final class ActionStrip {
@@ -271,11 +272,18 @@
 
   public final class CarText {
     method public static androidx.car.app.model.CarText create(CharSequence);
+    method @androidx.car.app.annotations.ExperimentalCarApi public java.util.List<java.lang.CharSequence!> getVariants();
     method public boolean isEmpty();
     method public static boolean isNullOrEmpty(androidx.car.app.model.CarText?);
     method public CharSequence toCharSequence();
   }
 
+  @androidx.car.app.annotations.ExperimentalCarApi public static final class CarText.Builder {
+    ctor public CarText.Builder(CharSequence);
+    method public androidx.car.app.model.CarText.Builder addVariant(CharSequence);
+    method public androidx.car.app.model.CarText build();
+  }
+
   @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(2) public final class ClickableSpan extends androidx.car.app.model.CarSpan {
     method public static androidx.car.app.model.ClickableSpan create(androidx.car.app.model.OnClickListener);
     method public androidx.car.app.model.OnClickDelegate getOnClickDelegate();
@@ -338,7 +346,9 @@
     method public androidx.car.app.model.GridItem.Builder setLoading(boolean);
     method public androidx.car.app.model.GridItem.Builder setOnClickListener(androidx.car.app.model.OnClickListener);
     method public androidx.car.app.model.GridItem.Builder setText(CharSequence);
+    method @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridItem.Builder setText(androidx.car.app.model.CarText);
     method public androidx.car.app.model.GridItem.Builder setTitle(CharSequence);
+    method @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.GridItem.Builder setTitle(androidx.car.app.model.CarText);
   }
 
   public final class GridTemplate implements androidx.car.app.model.Template {
@@ -419,6 +429,7 @@
 
   public static final class MessageTemplate.Builder {
     ctor public MessageTemplate.Builder(CharSequence);
+    ctor @androidx.car.app.annotations.ExperimentalCarApi public MessageTemplate.Builder(androidx.car.app.model.CarText);
     method public androidx.car.app.model.MessageTemplate.Builder addAction(androidx.car.app.model.Action);
     method public androidx.car.app.model.MessageTemplate build();
     method public androidx.car.app.model.MessageTemplate.Builder setDebugMessage(Throwable);
@@ -565,6 +576,7 @@
   public static final class Row.Builder {
     ctor public Row.Builder();
     method public androidx.car.app.model.Row.Builder addText(CharSequence);
+    method @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.Row.Builder addText(androidx.car.app.model.CarText);
     method public androidx.car.app.model.Row build();
     method public androidx.car.app.model.Row.Builder setBrowsable(boolean);
     method public androidx.car.app.model.Row.Builder setImage(androidx.car.app.model.CarIcon);
@@ -572,6 +584,7 @@
     method public androidx.car.app.model.Row.Builder setMetadata(androidx.car.app.model.Metadata);
     method public androidx.car.app.model.Row.Builder setOnClickListener(androidx.car.app.model.OnClickListener);
     method public androidx.car.app.model.Row.Builder setTitle(CharSequence);
+    method @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.model.Row.Builder setTitle(androidx.car.app.model.CarText);
     method public androidx.car.app.model.Row.Builder setToggle(androidx.car.app.model.Toggle);
   }
 
@@ -655,6 +668,87 @@
 
 }
 
+package androidx.car.app.model.signin {
+
+  @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(2) public final class InputSignInMethod implements androidx.car.app.model.signin.SignInTemplate.SignInMethod {
+    method public androidx.car.app.model.CarText? getDefaultValue();
+    method public int getInputType();
+    method public int getKeyboardType();
+    method public androidx.car.app.model.CarText? getMessage();
+    method public androidx.car.app.model.signin.OnInputCompletedDelegate getOnInputCompletedDelegate();
+    method public androidx.car.app.model.CarText? getPrompt();
+    method public boolean isShowKeyboardByDefault();
+    field public static final int INPUT_TYPE_DEFAULT = 1; // 0x1
+    field public static final int INPUT_TYPE_PASSWORD = 2; // 0x2
+    field public static final int KEYBOARD_DEFAULT = 1; // 0x1
+    field public static final int KEYBOARD_EMAIL = 2; // 0x2
+    field public static final int KEYBOARD_NUMBER = 4; // 0x4
+    field public static final int KEYBOARD_PHONE = 3; // 0x3
+  }
+
+  public static final class InputSignInMethod.Builder {
+    ctor public InputSignInMethod.Builder(androidx.car.app.model.signin.InputSignInMethod.OnInputCompletedListener);
+    method public androidx.car.app.model.signin.InputSignInMethod build();
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setDefaultValue(String);
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setInputType(int);
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setKeyboardType(int);
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setMessage(CharSequence);
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setPrompt(CharSequence);
+    method public androidx.car.app.model.signin.InputSignInMethod.Builder setShowKeyboardByDefault(boolean);
+  }
+
+  public static interface InputSignInMethod.OnInputCompletedListener {
+    method public void onInputCompleted(String);
+  }
+
+  @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(2) public interface OnInputCompletedDelegate {
+    method public void sendInputCompleted(String, androidx.car.app.OnDoneCallback);
+  }
+
+  @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(2) public final class PinSignInMethod implements androidx.car.app.model.signin.SignInTemplate.SignInMethod {
+    method public String getPin();
+  }
+
+  public static final class PinSignInMethod.Builder {
+    ctor public PinSignInMethod.Builder(String);
+    method public androidx.car.app.model.signin.PinSignInMethod build();
+  }
+
+  @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(2) public final class ProviderSignInMethod implements androidx.car.app.model.signin.SignInTemplate.SignInMethod {
+    method public androidx.car.app.model.Action getAction();
+  }
+
+  public static final class ProviderSignInMethod.Builder {
+    ctor public ProviderSignInMethod.Builder(androidx.car.app.model.Action);
+    method public androidx.car.app.model.signin.ProviderSignInMethod build();
+  }
+
+  @androidx.car.app.annotations.ExperimentalCarApi @androidx.car.app.annotations.RequiresCarApi(2) public final class SignInTemplate implements androidx.car.app.model.Template {
+    method public androidx.car.app.model.ActionStrip? getActionStrip();
+    method public java.util.List<androidx.car.app.model.Action!> getActions();
+    method public androidx.car.app.model.CarText? getAdditionalText();
+    method public androidx.car.app.model.Action? getHeaderAction();
+    method public androidx.car.app.model.CarText? getInstructions();
+    method public androidx.car.app.model.signin.SignInTemplate.SignInMethod getSignInMethod();
+    method public androidx.car.app.model.CarText? getTitle();
+  }
+
+  public static final class SignInTemplate.Builder {
+    ctor public SignInTemplate.Builder(androidx.car.app.model.signin.SignInTemplate.SignInMethod);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder addAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.signin.SignInTemplate build();
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setActionStrip(androidx.car.app.model.ActionStrip);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setAdditionalText(CharSequence);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setHeaderAction(androidx.car.app.model.Action);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setInstructions(CharSequence);
+    method public androidx.car.app.model.signin.SignInTemplate.Builder setTitle(CharSequence);
+  }
+
+  public static interface SignInTemplate.SignInMethod {
+  }
+
+}
+
 package androidx.car.app.navigation {
 
   public class NavigationManager {
@@ -787,9 +881,11 @@
 
   public static final class MessageInfo.Builder {
     ctor public MessageInfo.Builder(CharSequence);
+    ctor @androidx.car.app.annotations.ExperimentalCarApi public MessageInfo.Builder(androidx.car.app.model.CarText);
     method public androidx.car.app.navigation.model.MessageInfo build();
     method public androidx.car.app.navigation.model.MessageInfo.Builder setImage(androidx.car.app.model.CarIcon);
     method public androidx.car.app.navigation.model.MessageInfo.Builder setText(CharSequence);
+    method @androidx.car.app.annotations.ExperimentalCarApi public androidx.car.app.navigation.model.MessageInfo.Builder setText(androidx.car.app.model.CarText);
     method public androidx.car.app.navigation.model.MessageInfo.Builder setTitle(CharSequence);
   }
 
@@ -877,6 +973,7 @@
 
   public static final class Step.Builder {
     ctor public Step.Builder(CharSequence);
+    ctor @androidx.car.app.annotations.ExperimentalCarApi public Step.Builder(androidx.car.app.model.CarText);
     method public androidx.car.app.navigation.model.Step.Builder addLane(androidx.car.app.navigation.model.Lane);
     method public androidx.car.app.navigation.model.Step build();
     method public androidx.car.app.navigation.model.Step.Builder setCue(CharSequence);
diff --git a/car/app/app/build.gradle b/car/app/app/build.gradle
index 8f7fd1e..56dd2cfd 100644
--- a/car/app/app/build.gradle
+++ b/car/app/app/build.gradle
@@ -27,11 +27,12 @@
 
 dependencies {
     implementation("androidx.activity:activity:1.1.0")
-    implementation("androidx.annotation:annotation:1.2.0-beta01")
+    implementation("androidx.annotation:annotation:1.2.0-rc01")
     implementation("androidx.core:core:1.3.0")
     implementation("androidx.lifecycle:lifecycle-viewmodel:2.2.0")
-    implementation("androidx.lifecycle:lifecycle-common-java8:2.2.0")
-    implementation("androidx.annotation:annotation-experimental:1.1.0-beta01")
+    // Session and Screen both implement LifeCycleOwner so this needs to be exposed.
+    api("androidx.lifecycle:lifecycle-common-java8:2.2.0")
+    implementation("androidx.annotation:annotation-experimental:1.1.0-rc01")
     compileOnly KOTLIN_STDLIB // Due to :annotation-experimental
 
     annotationProcessor(NULLAWAY)
diff --git a/car/app/app/src/main/aidl/androidx/car/app/model/signin/IOnInputCompletedListener.aidl b/car/app/app/src/main/aidl/androidx/car/app/model/signin/IOnInputCompletedListener.aidl
new file mode 100644
index 0000000..e022dc3
--- /dev/null
+++ b/car/app/app/src/main/aidl/androidx/car/app/model/signin/IOnInputCompletedListener.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.car.app.model.signin;
+
+import androidx.car.app.IOnDoneCallback;
+
+/** @hide */
+oneway interface IOnInputCompletedListener {
+  void onInputCompleted(String value, IOnDoneCallback callback) = 1;
+}
diff --git a/car/app/app/src/main/java/androidx/car/app/CarAppService.java b/car/app/app/src/main/java/androidx/car/app/CarAppService.java
index 5bbb6e0..89f458c 100644
--- a/car/app/app/src/main/java/androidx/car/app/CarAppService.java
+++ b/car/app/app/src/main/java/androidx/car/app/CarAppService.java
@@ -87,7 +87,12 @@
  */
 public abstract class CarAppService extends Service {
     /**
-     * The {@link Intent} that must be declared as handled by the service.
+     * The full qualified name of the {@link CarAppService} class.
+     *
+     * <p>This is the same name that must be used to declare the action of the intent filter for
+     * the app's {@link CarAppService} in the app's manifest.
+     *
+     * @see CarAppService
      */
     public static final String SERVICE_INTERFACE = "androidx.car.app.CarAppService";
 
diff --git a/car/app/app/src/main/java/androidx/car/app/CarContext.java b/car/app/app/src/main/java/androidx/car/app/CarContext.java
index 1589314..6451e5d 100644
--- a/car/app/app/src/main/java/androidx/car/app/CarContext.java
+++ b/car/app/app/src/main/java/androidx/car/app/CarContext.java
@@ -51,8 +51,6 @@
 import androidx.lifecycle.LifecycleObserver;
 import androidx.lifecycle.LifecycleOwner;
 
-import org.jetbrains.annotations.NotNull;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.security.InvalidParameterException;
@@ -495,7 +493,7 @@
 
         LifecycleObserver observer = new DefaultLifecycleObserver() {
             @Override
-            public void onDestroy(@NonNull @NotNull LifecycleOwner owner) {
+            public void onDestroy(@NonNull LifecycleOwner owner) {
                 hostDispatcher.resetHosts();
                 owner.getLifecycle().removeObserver(this);
             }
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Action.java b/car/app/app/src/main/java/androidx/car/app/model/Action.java
index d9cc274..84347e7 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Action.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Action.java
@@ -34,6 +34,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.car.app.CarContext;
+import androidx.car.app.annotations.ExperimentalCarApi;
 import androidx.car.app.model.constraints.CarIconConstraints;
 import androidx.lifecycle.LifecycleOwner;
 
@@ -281,8 +282,6 @@
         /**
          * Sets the title to display in the action.
          *
-         * <p>Unless set with this method, the action will not have a title.
-         *
          * <p>Spans are not supported in the input string.
          *
          * @throws NullPointerException if {@code title} is {@code null}
@@ -295,6 +294,21 @@
         }
 
         /**
+         * Sets the title to display in the action.
+         *
+         * <p>Spans are not supported in the input string.
+         *
+         * @throws NullPointerException if {@code title} is {@code null}
+         * @see CarText
+         */
+        @ExperimentalCarApi
+        @NonNull
+        public Builder setTitle(@NonNull CarText title) {
+            mTitle = requireNonNull(title);
+            return this;
+        }
+
+        /**
          * Sets the icon to display in the action.
          *
          * <p>Unless set with this method, the action will not have an icon.
diff --git a/car/app/app/src/main/java/androidx/car/app/model/CarText.java b/car/app/app/src/main/java/androidx/car/app/model/CarText.java
index f30e582..b0e5c6b 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/CarText.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/CarText.java
@@ -18,6 +18,8 @@
 
 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
 
+import static java.util.Objects.requireNonNull;
+
 import android.text.SpannableString;
 import android.text.Spanned;
 
@@ -25,6 +27,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
+import androidx.car.app.annotations.ExperimentalCarApi;
 import androidx.car.app.utils.CollectionUtils;
 import androidx.car.app.utils.StringUtils;
 
@@ -63,12 +66,21 @@
  * <p>The {@link CarText#toString} method can be used to get a string representation of the string,
  * whereas the {@link CarText#toCharSequence()} method returns the reconstructed
  * {@link CharSequence}, with the non{@link CarSpan} spans removed.
+ *
+ * <p>The app is generally agnostic to the width of the views generated by the host that contain
+ * the text strings it supplies. For that reason, some models that take text allow the app to
+ * pass a list of text variants of different lengths. In those cases the host will pick the
+ * variant that best fits the screen. See {@link Builder#addVariant} for more information.
  */
 public final class CarText {
     @Keep
     private final String mText;
     @Keep
+    private final List<String> mTextVariants;
+    @Keep
     private final List<SpanWrapper> mSpans;
+    @Keep
+    private final List<List<SpanWrapper>> mSpansForVariants;
 
     /**
      * Returns {@code true} if the {@code carText} is {@code null} or an empty string, {@code
@@ -83,17 +95,28 @@
      *
      * <p>Only {@link CarSpan} type spans are allowed in a {@link CarText}, other spans will be
      * removed from the provided {@link CharSequence}.
+     *
+     * @throws NullPointerException if the text is {@code null}
      */
     @NonNull
     public static CarText create(@NonNull CharSequence text) {
-        return new CarText(text);
+        return new CarText(requireNonNull(text));
     }
 
-    /** Returns whether the text string is empty. */
+    /**
+     * Returns whether the text string is empty.
+     *
+     * <p>Only the first variant is checked.
+     */
     public boolean isEmpty() {
         return mText.isEmpty();
     }
 
+    /**
+     * Returns the string representation of the {@link CarText}.
+     *
+     * <p>Only the first variant is returned.
+     */
     @NonNull
     @Override
     public String toString() {
@@ -101,7 +124,7 @@
     }
 
     /**
-     * Returns the {@link CharSequence} corresponding to this text.
+     * Returns the {@link CharSequence} corresponding to the first text variant.
      *
      * <p>Spans that are not of type {@link CarSpan} that were passed when creating the
      * {@link CarText} instance will not be present in the returned {@link CharSequence}.
@@ -110,15 +133,32 @@
      */
     @NonNull
     public CharSequence toCharSequence() {
-        SpannableString spannableString = new SpannableString(mText == null ? "" : mText);
-        for (SpanWrapper spanWrapper : CollectionUtils.emptyIfNull(mSpans)) {
-            spannableString.setSpan(
-                    spanWrapper.getCarSpan(),
-                    spanWrapper.getStart(),
-                    spanWrapper.getEnd(),
-                    spanWrapper.getFlags());
+        return getCharSequence(mText, mSpans);
+    }
+
+    /**
+     * Returns the list of variants for this text.
+     *
+     * <p>Only the variants set with {@link Builder#addVariant(CharSequence)} will be returned.
+     * To get the first variant, use {@link CarText#toCharSequence}.
+     *
+     * <p>Spans that are not of type {@link CarSpan} that were passed when creating the
+     * {@link CarText} instance will not be present in the returned {@link CharSequence}.
+     *
+     * @see Builder#addVariant(CharSequence)
+     */
+    @ExperimentalCarApi
+    @NonNull
+    public List<CharSequence> getVariants() {
+        if (mTextVariants.isEmpty()) {
+            return Collections.emptyList();
         }
-        return spannableString;
+
+        List<CharSequence> charSequences = new ArrayList<>();
+        for (int i = 0; i < mTextVariants.size(); i++) {
+            charSequences.add(getCharSequence(mTextVariants.get(i), mSpansForVariants.get(i)));
+        }
+        return Collections.unmodifiableList(charSequences);
     }
 
     /**
@@ -135,11 +175,35 @@
     private CarText() {
         mText = "";
         mSpans = Collections.emptyList();
+        mTextVariants = Collections.emptyList();
+        mSpansForVariants = Collections.emptyList();
     }
 
-    private CarText(CharSequence text) {
+    CarText(CharSequence text) {
         mText = text.toString();
+        mSpans = getSpans(text);
+        mTextVariants = Collections.emptyList();
+        mSpansForVariants = Collections.emptyList();
+    }
 
+    @ExperimentalCarApi
+    CarText(Builder builder) {
+        mText = builder.mText.toString();
+        mSpans = getSpans(builder.mText);
+
+        List<CharSequence> textVariants = builder.mTextVariants;
+        List<String> textList = new ArrayList<>();
+        List<List<SpanWrapper>> spanList = new ArrayList<>();
+        for (int i = 0; i < textVariants.size(); i++) {
+            CharSequence text = textVariants.get(i);
+            textList.add(text.toString());
+            spanList.add(getSpans(text));
+        }
+        mTextVariants = CollectionUtils.unmodifiableCopy(textList);
+        mSpansForVariants = CollectionUtils.unmodifiableCopy(spanList);
+    }
+
+    private static List<SpanWrapper> getSpans(CharSequence text) {
         List<SpanWrapper> spans = new ArrayList<>();
         if (text instanceof Spanned) {
             Spanned spanned = (Spanned) text;
@@ -150,7 +214,19 @@
                 }
             }
         }
-        mSpans = CollectionUtils.unmodifiableCopy(spans);
+        return CollectionUtils.unmodifiableCopy(spans);
+    }
+
+    private static CharSequence getCharSequence(String text, List<SpanWrapper> spans) {
+        SpannableString spannableString = new SpannableString(text);
+        for (SpanWrapper spanWrapper : CollectionUtils.emptyIfNull(spans)) {
+            spannableString.setSpan(
+                    spanWrapper.getCarSpan(),
+                    spanWrapper.getStart(),
+                    spanWrapper.getEnd(),
+                    spanWrapper.getFlags());
+        }
+        return spannableString;
     }
 
     @Override
@@ -162,12 +238,15 @@
             return false;
         }
         CarText otherText = (CarText) other;
-        return Objects.equals(mText, otherText.mText) && Objects.equals(mSpans, otherText.mSpans);
+        return Objects.equals(mText, otherText.mText)
+                && Objects.equals(mSpans, otherText.mSpans)
+                && Objects.equals(mTextVariants, otherText.mTextVariants)
+                && Objects.equals(mSpansForVariants, otherText.mSpansForVariants);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mText, mSpans);
+        return Objects.hash(mText, mSpans, mTextVariants, mSpansForVariants);
     }
 
     /**
@@ -241,4 +320,56 @@
             return "[" + mCarSpan + ": " + mStart + ", " + mEnd + ", flags: " + mFlags + "]";
         }
     }
+
+    /** A builder of {@link CarText}. */
+    @ExperimentalCarApi
+    public static final class Builder {
+        @Keep
+        CharSequence mText;
+        @Keep
+        List<CharSequence> mTextVariants = new ArrayList<>();
+
+        /**
+         * Returns a new instance of a {@link Builder}.
+         *
+         * <p>Only {@link CarSpan} type spans are allowed in a {@link CarText}, other spans will be
+         * removed from the provided {@link CharSequence}.
+         *
+         * @param text the first variant of the text to use. This represents the app's preferred
+         *             text variant. Other alternatives can be supplied with
+         *             {@link Builder#addVariant}.
+         * @throws NullPointerException if the text is {@code null}
+         * @see Builder#addVariant(CharSequence)
+         */
+        public Builder(@NonNull CharSequence text) {
+            mText = requireNonNull(text);
+        }
+
+        /**
+         * Adds a text variant for the {@link CarText} instance.
+         *
+         * <p>Only {@link CarSpan} type spans are allowed in a {@link CarText}, other spans will be
+         * removed from the provided {@link CharSequence}.
+         *
+         * <p>The text variants should be added in order of preference, from most to least
+         * preferred (for instance, from longest to shortest). If the text provided via
+         * {@link #Builder} does not fit in the screen, the host will display the
+         * first variant that fits in the screen.
+         *
+         * @throws NullPointerException if the text is {@code null}
+         */
+        @NonNull
+        public Builder addVariant(@NonNull CharSequence text) {
+            mTextVariants.add(requireNonNull(text));
+            return this;
+        }
+
+        /**
+         * Constructs the {@link CarText} defined by this builder.
+         */
+        @NonNull
+        public CarText build() {
+            return new CarText(this);
+        }
+    }
 }
diff --git a/car/app/app/src/main/java/androidx/car/app/model/GridItem.java b/car/app/app/src/main/java/androidx/car/app/model/GridItem.java
index 640fd1c..954476e 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/GridItem.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/GridItem.java
@@ -29,6 +29,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.car.app.Screen;
+import androidx.car.app.annotations.ExperimentalCarApi;
 import androidx.car.app.model.constraints.CarIconConstraints;
 
 import java.lang.annotation.Retention;
@@ -227,9 +228,7 @@
         }
 
         /**
-         * Sets the title of the row.
-         *
-         * <p>Unless set with this method, the grid item will not have an title.
+         * Sets the title of the {@link GridItem}.
          *
          * <p>Spans are not supported in the input string.
          *
@@ -247,9 +246,25 @@
         }
 
         /**
-         * Sets a secondary text string to the grid item that is displayed below the title.
+         * Sets the title of the {@link GridItem}.
          *
-         * <p>Unless set with this method, the grid item will not have a secondary text string.
+         * <p>Spans are not supported in the input string.
+         *
+         * @throws NullPointerException     if {@code title} is {@code null}
+         * @throws IllegalArgumentException if {@code title} is empty
+         */
+        @ExperimentalCarApi
+        @NonNull
+        public Builder setTitle(@NonNull CarText title) {
+            if (CarText.isNullOrEmpty(title)) {
+                throw new IllegalArgumentException("The title cannot be null or empty");
+            }
+            mTitle = title;
+            return this;
+        }
+
+        /**
+         * Sets a secondary text string to the grid item that is displayed below the title.
          *
          * <p>The text's color can be customized with {@link ForegroundCarColorSpan} instances, any
          * other spans will be ignored by the host.
@@ -267,6 +282,25 @@
         }
 
         /**
+         * Sets a secondary text string to the grid item that is displayed below the title.
+         *
+         * <p>The text's color can be customized with {@link ForegroundCarColorSpan} instances, any
+         * other spans will be ignored by the host.
+         *
+         * <h2>Text Wrapping</h2>
+         *
+         * This text is truncated at the end to fit in a single line below the title
+         *
+         * @throws NullPointerException if {@code text} is {@code null}
+         */
+        @ExperimentalCarApi
+        @NonNull
+        public Builder setText(@NonNull CarText text) {
+            mText = requireNonNull(text);
+            return this;
+        }
+
+        /**
          * Sets an image to show in the grid item with the default size {@link #IMAGE_TYPE_LARGE}.
          *
          * @throws NullPointerException if {@code image} is {@code null}
diff --git a/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java b/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java
index 48010f8..c89e5a2 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java
@@ -26,6 +26,7 @@
 import androidx.annotation.Keep;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.car.app.annotations.ExperimentalCarApi;
 import androidx.car.app.model.constraints.CarIconConstraints;
 import androidx.car.app.utils.CollectionUtils;
 
@@ -344,5 +345,16 @@
         public Builder(@NonNull CharSequence message) {
             mMessage = CarText.create(requireNonNull(message));
         }
+
+        /**
+         * Returns a {@link Builder} instance.
+         *
+         * @param message the text message to display in the template
+         * @throws NullPointerException if the {@code message} is {@code null}
+         */
+        @ExperimentalCarApi
+        public Builder(@NonNull CarText message) {
+            mMessage = requireNonNull(message);
+        }
     }
 }
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Row.java b/car/app/app/src/main/java/androidx/car/app/model/Row.java
index ea87d36..8725f50 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Row.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Row.java
@@ -29,6 +29,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
+import androidx.car.app.annotations.ExperimentalCarApi;
 import androidx.car.app.model.constraints.CarIconConstraints;
 import androidx.car.app.utils.CollectionUtils;
 
@@ -309,6 +310,21 @@
         }
 
         /**
+         * Sets the title of the row.
+         *
+         * @throws IllegalArgumentException if {@code title} is {@code null} or empty
+         */
+        @ExperimentalCarApi
+        @NonNull
+        public Builder setTitle(@NonNull CarText title) {
+            if (requireNonNull(title).isEmpty()) {
+                throw new IllegalArgumentException("The title cannot be null or empty");
+            }
+            mTitle = title;
+            return this;
+        }
+
+        /**
          * Adds a text string to the row below the title.
          *
          * <p>The text's color can be customized with {@link ForegroundCarColorSpan} instances, any
@@ -380,6 +396,19 @@
         }
 
         /**
+         * Adds a text string to the row below the title.
+         *
+         * @throws NullPointerException if {@code text} is {@code null}
+         * @see Builder#addText(CharSequence)
+         */
+        @ExperimentalCarApi
+        @NonNull
+        public Builder addText(@NonNull CarText text) {
+            mTexts.add(requireNonNull(text));
+            return this;
+        }
+
+        /**
          * Sets an image to show in the row with the default size {@link #IMAGE_TYPE_SMALL}.
          *
          * @throws NullPointerException if {@code image} is {@code null}
diff --git a/car/app/app/src/main/java/androidx/car/app/model/signin/InputSignInMethod.java b/car/app/app/src/main/java/androidx/car/app/model/signin/InputSignInMethod.java
new file mode 100644
index 0000000..bb20597
--- /dev/null
+++ b/car/app/app/src/main/java/androidx/car/app/model/signin/InputSignInMethod.java
@@ -0,0 +1,425 @@
+/*
+ * 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.car.app.model.signin;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.SuppressLint;
+import android.os.Looper;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.Keep;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.car.app.annotations.ExperimentalCarApi;
+import androidx.car.app.annotations.RequiresCarApi;
+import androidx.car.app.model.CarText;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * A {@link SignInTemplate.SignInMethod} that presents an input box for the user to enter their
+ * credentials.
+ *
+ * <p>For example, this can be used to request a username, a password or an activation code.
+ */
+@ExperimentalCarApi
+@RequiresCarApi(2)
+public final class InputSignInMethod implements SignInTemplate.SignInMethod {
+    /** A listener for handling text input completion event. */
+    public interface OnInputCompletedListener {
+        /**
+         * Notifies when the user finished entering text in an input box.
+         *
+         * <p>This event is sent when the user finishes typing in the keyboard and pressed enter.
+         * If the user simply stops typing and closes the keyboard, this event will not be sent.
+         *
+         * @param text the text that was entered, or an empty string if no text was typed.
+         */
+        void onInputCompleted(@NonNull String text);
+    }
+
+    /**
+     * The type of input represented by the {@link InputSignInMethod} instance.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    @IntDef(
+            value = {
+                    INPUT_TYPE_DEFAULT,
+                    INPUT_TYPE_PASSWORD,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface InputType {
+    }
+
+    /**
+     * Default input where the text is shown as it is typed.
+     */
+    public static final int INPUT_TYPE_DEFAULT = 1;
+
+    /**
+     * Input where the text is hidden as it is typed.
+     */
+    public static final int INPUT_TYPE_PASSWORD = 2;
+
+    /**
+     * The type of keyboard to be displayed while the user is interacting with this input.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY)
+    @IntDef(
+            value = {
+                    KEYBOARD_DEFAULT,
+                    KEYBOARD_EMAIL,
+                    KEYBOARD_PHONE,
+                    KEYBOARD_NUMBER,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface KeyboardType {
+    }
+
+    /**
+     * Default (full) keyboard.
+     */
+    public static final int KEYBOARD_DEFAULT = 1;
+
+    /**
+     * Keyboard optimized for typing an email address.
+     */
+    public static final int KEYBOARD_EMAIL = 2;
+
+    /**
+     * Keyboard optimized for typing a phone number.
+     */
+    public static final int KEYBOARD_PHONE = 3;
+
+    /**
+     * Keyboard optimized for typing numbers.
+     */
+    public static final int KEYBOARD_NUMBER = 4;
+
+    @Keep
+    @Nullable
+    private final CarText mPrompt;
+    @Keep
+    @Nullable
+    private final CarText mDefaultValue;
+    @Keep
+    @InputType
+    private final int mInputType;
+    @Keep
+    @Nullable
+    private final CarText mMessage;
+    @Keep
+    @KeyboardType
+    private final int mKeyboardType;
+    @Keep
+    @Nullable
+    private final OnInputCompletedDelegate mOnInputCompletedDelegate;
+    @Keep
+    private final boolean mShowKeyboardByDefault;
+
+    /**
+     * Returns the text explaining to the user what should be entered in this input box or
+     * {@code null} if no prompt is provided.
+     *
+     * @see Builder#setPrompt(CharSequence)
+     */
+    @Nullable
+    public CarText getPrompt() {
+        return mPrompt;
+    }
+
+    /**
+     * Returns the default value for this input box or {@code null} if no value is provided.
+     *
+     * <p>For the {@link #INPUT_TYPE_PASSWORD} input type, this value will formatted to be hidden
+     * to the user as well.
+     *
+     * @see Builder#setDefaultValue(String)
+     */
+    @Nullable
+    public CarText getDefaultValue() {
+        return mDefaultValue;
+    }
+
+    /**
+     * Returns the input type, one of {@link #INPUT_TYPE_DEFAULT} or {@link #INPUT_TYPE_PASSWORD}
+     */
+    @InputType
+    public int getInputType() {
+        return mInputType;
+    }
+
+    /**
+     * Returns a message associated with the user input.
+     *
+     * <p>For example, this can be used to indicate formatting errors, wrong username or
+     * password, or any other situation related to the user input.
+     *
+     * @see Builder#setMessage(CharSequence)
+     */
+    @Nullable
+    public CarText getMessage() {
+        return mMessage;
+    }
+
+    /**
+     * Returns the type of keyboard to be displayed when this input gets focused.
+     *
+     * @see Builder#setKeyboardType(int)
+     */
+    public int getKeyboardType() {
+        return mKeyboardType;
+    }
+
+    /**
+     * Returns the {@link OnInputCompletedDelegate} for input callbacks.
+     *
+     * @see Builder#Builder(OnInputCompletedListener)
+     */
+    @NonNull
+    public OnInputCompletedDelegate getOnInputCompletedDelegate() {
+        return requireNonNull(mOnInputCompletedDelegate);
+    }
+
+    /**
+     * Returns whether to show the keyboard by default or not.
+     *
+     * @see Builder#setShowKeyboardByDefault
+     */
+    public boolean isShowKeyboardByDefault() {
+        return mShowKeyboardByDefault;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "[inputType:" + mInputType + ", keyboardType: " + mKeyboardType + "]";
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        if (!(other instanceof InputSignInMethod)) {
+            return false;
+        }
+
+        InputSignInMethod that = (InputSignInMethod) other;
+        return mInputType == that.mInputType
+                && mKeyboardType == that.mKeyboardType
+                && mShowKeyboardByDefault == that.mShowKeyboardByDefault
+                && Objects.equals(mPrompt, that.mPrompt)
+                && Objects.equals(mDefaultValue, that.mDefaultValue)
+                && Objects.equals(mMessage, that.mMessage);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPrompt, mDefaultValue, mInputType, mMessage, mKeyboardType,
+                mShowKeyboardByDefault);
+    }
+
+    InputSignInMethod(Builder builder) {
+        mPrompt = builder.mPrompt;
+        mDefaultValue = builder.mDefaultValue;
+        mInputType = builder.mInputType;
+        mMessage = builder.mMessage;
+        mKeyboardType = builder.mKeyboardType;
+        mOnInputCompletedDelegate = builder.mOnInputCompletedDelegate;
+        mShowKeyboardByDefault = builder.mShowKeyboardByDefault;
+    }
+
+    /** Constructs an empty instance, used by serialization code. */
+    private InputSignInMethod() {
+        mPrompt = null;
+        mDefaultValue = null;
+        mInputType = INPUT_TYPE_DEFAULT;
+        mMessage = null;
+        mKeyboardType = KEYBOARD_DEFAULT;
+        mOnInputCompletedDelegate = null;
+        mShowKeyboardByDefault = false;
+    }
+
+    /** A builder of {@link InputSignInMethod}. */
+    public static final class Builder {
+        final OnInputCompletedDelegate mOnInputCompletedDelegate;
+        @Nullable
+        CarText mPrompt;
+        @Nullable
+        CarText mDefaultValue;
+        int mInputType = INPUT_TYPE_DEFAULT;
+        @Nullable
+        CarText mMessage;
+        int mKeyboardType = KEYBOARD_DEFAULT;
+        boolean mShowKeyboardByDefault;
+
+        /**
+         * Sets the text explaining to the user what should be entered in this input box.
+         *
+         * <p>Unless set with this method, the sign-in method will not show any prompt.
+         *
+         * <p>Spans are supported in the input string.
+         *
+         * @throws NullPointerException if {@code prompt} is {@code null}
+         */
+        // TODO(b/181569051): document supported span types.
+        @NonNull
+        public Builder setPrompt(@NonNull CharSequence instructions) {
+            mPrompt = CarText.create(requireNonNull(instructions));
+            return this;
+        }
+
+        /**
+         * Sets the default value for this input.
+         *
+         * <p>Unless set with this method, the input box will not have a default value.
+         *
+         * <p>For {@link #INPUT_TYPE_PASSWORD} input types, in order to indicate that is not empty
+         * it is recommended to use a special value rather the actual credential. Any user input
+         * on a {@link #INPUT_TYPE_PASSWORD} input box will replace this default value instead of
+         * appending to it.
+         *
+         * @throws NullPointerException if {@code defaultValue} is {@code null}
+         */
+        @NonNull
+        public Builder setDefaultValue(@NonNull String defaultValue) {
+            mDefaultValue = CarText.create(requireNonNull(defaultValue));
+            return this;
+        }
+
+        /**
+         * Sets the input type.
+         *
+         * <p>This must be one of {@link InputSignInMethod#INPUT_TYPE_DEFAULT} or
+         * {@link InputSignInMethod#INPUT_TYPE_PASSWORD}
+         *
+         * <p>If not set, {@link InputSignInMethod#INPUT_TYPE_DEFAULT} will be assumed.
+         *
+         * @throws IllegalArgumentException if the provided input type is not supported
+         */
+        @NonNull
+        public Builder setInputType(@InputType int inputType) {
+            mInputType = validateInputType(inputType);
+            return this;
+        }
+
+        /**
+         * Sets the message associated with this input box.
+         *
+         * <p>For example, this can be used to indicate formatting errors, wrong username or
+         * password or any other situation related to the user input.
+         *
+         * <h4>Requirements</h4>
+         *
+         * Messages can have only up to 2 lines of text, amd additional texts beyond the
+         * second line may be truncated.
+         *
+         * <p>Spans are supported in the input string.
+         *
+         * @throws NullPointerException if {@code message} is {@code null}
+         */
+        // TODO(b/181569051): document supported span types.
+        @NonNull
+        public Builder setMessage(@NonNull CharSequence message) {
+            mMessage = CarText.create(requireNonNull(message));
+            return this;
+        }
+
+        /**
+         * Sets the keyboard type to display when this input box gets focused.
+         *
+         * <p>This must be one of {@link #KEYBOARD_DEFAULT}, {@link #KEYBOARD_PHONE},
+         * {@link #KEYBOARD_NUMBER}, or {@link #KEYBOARD_EMAIL}. A host might fall back
+         * to {@link #KEYBOARD_DEFAULT} if they do not support a particular keyboard type.
+         *
+         * If not provided, {@link #KEYBOARD_DEFAULT} will be used.
+         *
+         * @throws IllegalArgumentException if the provided type is not supported
+         */
+        @NonNull
+        public Builder setKeyboardType(@KeyboardType int keyboardType) {
+            mKeyboardType = validateKeyboardType(keyboardType);
+            return this;
+        }
+
+        /**
+         * Sets whether keyboard should be opened by default when this template is presented.
+         *
+         * By default, keyboard will only be opened if the user focuses on the input box.
+         */
+        @NonNull
+        public Builder setShowKeyboardByDefault(boolean showKeyboardByDefault) {
+            mShowKeyboardByDefault = showKeyboardByDefault;
+            return this;
+        }
+
+        /**
+         * Builds an {@link InputSignInMethod} instance.
+         */
+        @NonNull
+        public InputSignInMethod build() {
+            return new InputSignInMethod(this);
+        }
+
+        /**
+         * Returns an {@link InputSignInMethod.Builder} instance.
+         *
+         * <p>Note that the listener relates to UI events and will be executed on the main thread
+         * using {@link Looper#getMainLooper()}.
+         *
+         * @param listener the {@link OnInputCompletedListener} to be notified of input events
+         * @throws NullPointerException if {@code listener} is {@code null}
+         */
+        @SuppressLint("ExecutorRegistration")
+        public Builder(@NonNull OnInputCompletedListener listener) {
+            mOnInputCompletedDelegate = OnInputCompletedDelegateImpl.create(
+                    requireNonNull(listener));
+        }
+
+        @KeyboardType
+        private static int validateKeyboardType(@KeyboardType int keyboardType) {
+            if (keyboardType != KEYBOARD_DEFAULT && keyboardType != KEYBOARD_EMAIL
+                    && keyboardType != KEYBOARD_NUMBER && keyboardType != KEYBOARD_PHONE) {
+                throw new IllegalArgumentException("Keyboard type is not supported: "
+                        + keyboardType);
+            }
+
+            return keyboardType;
+        }
+
+        @InputType
+        private static int validateInputType(@InputType int inputType) {
+            if (inputType != INPUT_TYPE_DEFAULT && inputType != INPUT_TYPE_PASSWORD) {
+                throw new IllegalArgumentException("Invalid input type: " + inputType);
+            }
+
+            return inputType;
+        }
+    }
+}
diff --git a/car/app/app/src/main/java/androidx/car/app/model/signin/OnInputCompletedDelegate.java b/car/app/app/src/main/java/androidx/car/app/model/signin/OnInputCompletedDelegate.java
new file mode 100644
index 0000000..73bf42a
--- /dev/null
+++ b/car/app/app/src/main/java/androidx/car/app/model/signin/OnInputCompletedDelegate.java
@@ -0,0 +1,42 @@
+/*
+ * 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.car.app.model.signin;
+
+import android.annotation.SuppressLint;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.OnDoneCallback;
+import androidx.car.app.annotations.ExperimentalCarApi;
+import androidx.car.app.annotations.RequiresCarApi;
+
+/**
+ * A host-side interface for reporting text input events to clients.
+ */
+@ExperimentalCarApi
+@RequiresCarApi(2)
+public interface OnInputCompletedDelegate {
+    /**
+     * Notifies that user input has completed.
+     *
+     * @param value    the text entered
+     * @param callback the {@link OnDoneCallback} to trigger when the client finishes handling
+     *                 the event
+     */
+    // This mirrors the AIDL class and is not supposed to support an executor as an input.
+    @SuppressLint("ExecutorRegistration")
+    void sendInputCompleted(@NonNull String value, @NonNull OnDoneCallback callback);
+}
diff --git a/car/app/app/src/main/java/androidx/car/app/model/signin/OnInputCompletedDelegateImpl.java b/car/app/app/src/main/java/androidx/car/app/model/signin/OnInputCompletedDelegateImpl.java
new file mode 100644
index 0000000..d9d5141
--- /dev/null
+++ b/car/app/app/src/main/java/androidx/car/app/model/signin/OnInputCompletedDelegateImpl.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.car.app.model.signin;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.SuppressLint;
+import android.os.RemoteException;
+
+import androidx.annotation.Keep;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.car.app.IOnDoneCallback;
+import androidx.car.app.OnDoneCallback;
+import androidx.car.app.annotations.ExperimentalCarApi;
+import androidx.car.app.model.signin.InputSignInMethod.OnInputCompletedListener;
+import androidx.car.app.utils.RemoteUtils;
+
+/**
+ * Implementation class for {@link OnInputCompletedDelegate} to allow IPC for text-input-related
+ * events.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+@ExperimentalCarApi
+public class OnInputCompletedDelegateImpl implements OnInputCompletedDelegate {
+
+    @Keep
+    @Nullable
+    private final IOnInputCompletedListener mListener;
+
+    @Override
+    public void sendInputCompleted(@NonNull String text, @NonNull OnDoneCallback callback) {
+        try {
+            requireNonNull(mListener).onInputCompleted(text,
+                    RemoteUtils.createOnDoneCallbackStub(callback));
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    // This mirrors the AIDL class and is not supposed to support an executor as an input.
+    @SuppressLint("ExecutorRegistration")
+    @NonNull
+    static OnInputCompletedDelegate create(@NonNull OnInputCompletedListener listener) {
+        return new OnInputCompletedDelegateImpl(requireNonNull(listener));
+    }
+
+    private OnInputCompletedDelegateImpl(@NonNull OnInputCompletedListener listener) {
+        mListener = new OnInputCompletedStub(listener);
+    }
+
+    /** For serialization. */
+    private OnInputCompletedDelegateImpl() {
+        mListener = null;
+    }
+
+    @Keep // We need to keep these stub for Bundler serialization logic.
+    private static class OnInputCompletedStub extends IOnInputCompletedListener.Stub {
+        private final OnInputCompletedListener mListener;
+
+        OnInputCompletedStub(OnInputCompletedListener listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void onInputCompleted(String value, IOnDoneCallback callback) {
+            RemoteUtils.dispatchCallFromHost(callback, "onInputCompleted",
+                    () -> {
+                        mListener.onInputCompleted(value);
+                        return null;
+
+                    });
+        }
+    }
+}
diff --git a/car/app/app/src/main/java/androidx/car/app/model/signin/PinSignInMethod.java b/car/app/app/src/main/java/androidx/car/app/model/signin/PinSignInMethod.java
new file mode 100644
index 0000000..a44e7c7
--- /dev/null
+++ b/car/app/app/src/main/java/androidx/car/app/model/signin/PinSignInMethod.java
@@ -0,0 +1,110 @@
+/*
+ * 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.car.app.model.signin;
+
+import static java.util.Objects.requireNonNull;
+
+import android.text.TextUtils;
+
+import androidx.annotation.Keep;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.car.app.annotations.ExperimentalCarApi;
+import androidx.car.app.annotations.RequiresCarApi;
+
+import java.util.Objects;
+
+/**
+ * A {@link SignInTemplate.SignInMethod} that presents a PIN or activation code that the user can
+ * use to sign-in.
+ */
+@ExperimentalCarApi
+@RequiresCarApi(2)
+public final class PinSignInMethod implements SignInTemplate.SignInMethod {
+    @Keep
+    @Nullable
+    private final String mPin;
+
+    /**
+     * Returns the PIN or activation code to present to the user or {@code null} if not set.
+     *
+     * @see Builder#Builder(String)
+     */
+    @NonNull
+    public String getPin() {
+        return requireNonNull(mPin);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        if (!(other instanceof PinSignInMethod)) {
+            return false;
+        }
+
+        PinSignInMethod that = (PinSignInMethod) other;
+        return Objects.equals(mPin, that.mPin);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPin);
+    }
+
+    PinSignInMethod(Builder builder) {
+        mPin = builder.mPin;
+    }
+
+    /** Constructs an empty instance, used by serialization code. */
+    private PinSignInMethod() {
+        mPin = null;
+    }
+
+    /** A builder of {@link PinSignInMethod}. */
+    public static final class Builder {
+        final String mPin;
+
+        /**
+         * Returns a {@link PinSignInMethod} instance.
+         */
+        @NonNull
+        public PinSignInMethod build() {
+            return new PinSignInMethod(this);
+        }
+
+        /**
+         * Returns a {@link PinSignInMethod.Builder} instance.
+         *
+         * <p>The provided pin must be no more than 20 characters long. To facilitate typing this
+         * code, it is recommended restricting the string to a limited set (for example, numbers,
+         * upper-case letters, hexadecimal, etc.).
+         *
+         * @param pin the PIN to display
+         * @throws IllegalArgumentException if {@code pin} is {@code null} or empty
+         */
+        // TODO(b/182309112): follow up on how to enforce the 20-character limit.
+        public Builder(@NonNull String pin) {
+            if (TextUtils.isEmpty(pin)) {
+                throw new IllegalArgumentException("PIN must not be empty");
+            }
+            mPin = pin;
+        }
+    }
+}
diff --git a/car/app/app/src/main/java/androidx/car/app/model/signin/ProviderSignInMethod.java b/car/app/app/src/main/java/androidx/car/app/model/signin/ProviderSignInMethod.java
new file mode 100644
index 0000000..a49821a
--- /dev/null
+++ b/car/app/app/src/main/java/androidx/car/app/model/signin/ProviderSignInMethod.java
@@ -0,0 +1,128 @@
+/*
+ * 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.car.app.model.signin;
+
+import static java.util.Objects.requireNonNull;
+
+import androidx.annotation.Keep;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.car.app.annotations.ExperimentalCarApi;
+import androidx.car.app.annotations.RequiresCarApi;
+import androidx.car.app.model.Action;
+
+import java.util.Objects;
+
+/**
+ * A {@link SignInTemplate.SignInMethod} that allows the user to initiate sign-in with a
+ * authentication provider.
+ *
+ * <p>Not all providers will be available on all devices. It is the developer's responsibility to
+ * verify the presence of the corresponding provider by using the provider's own APIs. For
+ * example, for Google Sign In, check
+ * <a href="https://developers.google.com/identity/sign-in/android/sign-in">Integrating Google
+ * Sign-In into Your Android App</a>).
+ */
+@ExperimentalCarApi
+@RequiresCarApi(2)
+public final class ProviderSignInMethod implements SignInTemplate.SignInMethod {
+    @Keep
+    @Nullable
+    private final Action mAction;
+
+    /**
+     * Returns the {@link Action} the user can use to initiate the sign-in with a given provider
+     * or {@code null} if not set.
+     *
+     * @see Builder#Builder(Action)
+     */
+    @NonNull
+    public Action getAction() {
+        return requireNonNull(mAction);
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "[action:" + mAction + "]";
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        if (!(other instanceof ProviderSignInMethod)) {
+            return false;
+        }
+
+        ProviderSignInMethod that = (ProviderSignInMethod) other;
+        return Objects.equals(mAction, that.mAction);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAction);
+    }
+
+    ProviderSignInMethod(Builder builder) {
+        mAction = builder.mAction;
+    }
+
+    /** Constructs an empty instance, used by serialization code. */
+    private ProviderSignInMethod() {
+        mAction = null;
+    }
+
+    /** A builder of {@link ProviderSignInMethod}. */
+    public static final class Builder {
+        final Action mAction;
+
+        /**
+         * Returns a {@link ProviderSignInMethod} instance.
+         */
+        @NonNull
+        public ProviderSignInMethod build() {
+            return new ProviderSignInMethod(this);
+        }
+
+        /**
+         * Returns a {@link ProviderSignInMethod.Builder} instance.
+         *
+         * <h4>Requirements</h4>
+         *
+         * The provider action must not be a standard action, and it must use a
+         * {@link androidx.car.app.model.ParkedOnlyOnClickListener}.
+         *
+         * @throws IllegalArgumentException if {@code action} does not meet the requirements
+         * @throws NullPointerException     if {@code action} is {@code null}
+         * @see Action
+         * @see androidx.car.app.model.ParkedOnlyOnClickListener
+         */
+        public Builder(@NonNull Action action) {
+            if (requireNonNull(action).getType() != Action.TYPE_CUSTOM) {
+                throw new IllegalArgumentException("The action must not be a standard action");
+            }
+            if (!requireNonNull(action.getOnClickDelegate()).isParkedOnly()) {
+                throw new IllegalArgumentException("The action must use a "
+                        + "ParkedOnlyOnClickListener");
+            }
+            mAction = action;
+        }
+    }
+}
diff --git a/car/app/app/src/main/java/androidx/car/app/model/signin/SignInTemplate.java b/car/app/app/src/main/java/androidx/car/app/model/signin/SignInTemplate.java
new file mode 100644
index 0000000..1af381d
--- /dev/null
+++ b/car/app/app/src/main/java/androidx/car/app/model/signin/SignInTemplate.java
@@ -0,0 +1,383 @@
+/*
+ * 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.car.app.model.signin;
+
+import static androidx.car.app.model.constraints.ActionsConstraints.ACTIONS_CONSTRAINTS_HEADER;
+import static androidx.car.app.model.constraints.ActionsConstraints.ACTIONS_CONSTRAINTS_SIMPLE;
+
+import static java.util.Objects.requireNonNull;
+
+import androidx.annotation.Keep;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.car.app.Screen;
+import androidx.car.app.annotations.ExperimentalCarApi;
+import androidx.car.app.annotations.RequiresCarApi;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.CarText;
+import androidx.car.app.model.Template;
+import androidx.car.app.utils.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A template that can be used to create a sign-in flow.
+ *
+ * <h4>Template Restrictions</h4>
+ *
+ * This template is considered the start of a new task and thus restarts the template quota when an
+ * app reaches this template. If this template is sent consecutively, subsequent
+ * {@link SignInTemplate}s will not trigger a quota reset, as they will be considered part of the
+ * same sign-in flow. The quota will be reduced for these templates unless they are considered
+ * a refresh of a previous one.
+ *
+ * This template is considered a refresh of a previous one if:
+ *
+ * <ul>
+ *   <li>The template title and the sign-in method have not changed.
+ * </ul>
+ *
+ * @see Screen#onGetTemplate()
+ */
+@ExperimentalCarApi
+@RequiresCarApi(2)
+public final class SignInTemplate implements Template {
+    /**
+     * One of the possible sign in methods that can be set on a {@link SignInTemplate}.
+     */
+    public interface SignInMethod {
+    }
+
+    private static final int MAX_ACTIONS_ALLOWED = 2;
+
+    @Keep
+    @Nullable
+    private final Action mHeaderAction;
+    @Keep
+    @Nullable
+    private final CarText mTitle;
+    @Keep
+    @Nullable
+    private final CarText mInstructions;
+    @Keep
+    @Nullable
+    private final CarText mAdditionalText;
+    @Keep
+    @Nullable
+    private final ActionStrip mActionStrip;
+    @Keep
+    private final List<Action> mActionList;
+    @Keep
+    @Nullable
+    private final SignInMethod mSignInMethod;
+
+    /**
+     * Returns the title of the template or {@code null} if not set.
+     *
+     * @see Builder#setTitle(CharSequence)
+     */
+    @Nullable
+    public CarText getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Returns the {@link Action} that is set to be displayed in the header of the template or
+     * {@code null} if not set.
+     *
+     * @see Builder#setHeaderAction(Action)
+     */
+    @Nullable
+    public Action getHeaderAction() {
+        return mHeaderAction;
+    }
+
+    /**
+     * Returns a text containing instructions on how to sign in or {@code null} if not set.
+     *
+     * @see Builder#setInstructions(CharSequence)
+     */
+    @Nullable
+    public CarText getInstructions() {
+        return mInstructions;
+    }
+
+    /**
+     * Returns any additional text that needs to be displayed in the template or {@code null} if
+     * not set.
+     *
+     * @see Builder#setAdditionalText(CharSequence)
+     */
+    @Nullable
+    public CarText getAdditionalText() {
+        return mAdditionalText;
+    }
+
+    /**
+     * Returns the {@link ActionStrip} for this template or {@code null} if not set.
+     *
+     * @see Builder#setActionStrip(ActionStrip)
+     */
+    @Nullable
+    public ActionStrip getActionStrip() {
+        return mActionStrip;
+    }
+
+    /**
+     * Returns the list of {@link Action}s displayed alongside the {@link SignInMethod} in this
+     * template.
+     *
+     * @see Builder#addAction(Action)
+     */
+    @NonNull
+    public List<Action> getActions() {
+        return CollectionUtils.emptyIfNull(mActionList);
+    }
+
+    /**
+     * Returns the sign-in method of this template.
+     *
+     * @see Builder#Builder(SignInMethod)
+     */
+    @NonNull
+    public SignInMethod getSignInMethod() {
+        return requireNonNull(mSignInMethod);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        if (!(other instanceof SignInTemplate)) {
+            return false;
+        }
+
+        SignInTemplate that = (SignInTemplate) other;
+        return Objects.equals(mHeaderAction, that.mHeaderAction)
+                && Objects.equals(mTitle, that.mTitle)
+                && Objects.equals(mInstructions, that.mInstructions)
+                && Objects.equals(mAdditionalText, that.mAdditionalText)
+                && Objects.equals(mActionStrip, that.mActionStrip)
+                && Objects.equals(mActionList, that.mActionList)
+                && Objects.equals(mSignInMethod, that.mSignInMethod);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                mHeaderAction,
+                mTitle,
+                mInstructions,
+                mAdditionalText,
+                mActionStrip,
+                mActionList,
+                mSignInMethod);
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "SignInTemplate";
+    }
+
+    SignInTemplate(Builder builder) {
+        mHeaderAction = builder.mHeaderAction;
+        mTitle = builder.mTitle;
+        mInstructions = builder.mInstructions;
+        mAdditionalText = builder.mAdditionalText;
+        mActionStrip = builder.mActionStrip;
+        mActionList = CollectionUtils.unmodifiableCopy(builder.mActionList);
+        mSignInMethod = builder.mSignInMethod;
+    }
+
+    /** Constructs an empty instance, used by serialization code. */
+    private SignInTemplate() {
+        mHeaderAction = null;
+        mTitle = null;
+        mInstructions = null;
+        mAdditionalText = null;
+        mActionStrip = null;
+        mActionList = Collections.emptyList();
+        mSignInMethod = null;
+    }
+
+    /** A builder of {@link SignInTemplate}. */
+    public static final class Builder {
+        final SignInMethod mSignInMethod;
+        @Nullable
+        Action mHeaderAction;
+        @Nullable
+        CarText mTitle;
+        @Nullable
+        CarText mInstructions;
+        @Nullable
+        CarText mAdditionalText;
+        @Nullable
+        ActionStrip mActionStrip;
+        List<Action> mActionList = new ArrayList<>();
+
+        /**
+         * Sets the {@link Action} that will be displayed in the header of the template.
+         *
+         * <p>Unless set with this method, the template will not have a header action.
+         *
+         * <h4>Requirements</h4>
+         *
+         * This template only supports either one of {@link Action#APP_ICON} and
+         * {@link Action#BACK} as a header {@link Action}.
+         *
+         * @throws IllegalArgumentException if {@code headerAction} does not meet the template's
+         *                                  requirements
+         * @throws NullPointerException     if {@code headerAction} is {@code null}
+         */
+        @NonNull
+        public Builder setHeaderAction(@NonNull Action headerAction) {
+            ACTIONS_CONSTRAINTS_HEADER.validateOrThrow(
+                    Collections.singletonList(requireNonNull(headerAction)));
+            mHeaderAction = headerAction;
+            return this;
+        }
+
+        /**
+         * Sets the {@link ActionStrip} for this template.
+         *
+         * <p>Unless set with this method, the template will not have an action strip.
+         *
+         * <h4>Requirements</h4>
+         *
+         * This template allows up to 2 {@link Action}s in its {@link ActionStrip}. Of the 2 allowed
+         * {@link Action}s, one of them can contain a title as set via
+         * {@link Action.Builder#setTitle}. Otherwise, only {@link Action}s with icons are allowed.
+         *
+         * @throws IllegalArgumentException if {@code actionStrip} does not meet the requirements
+         * @throws NullPointerException     if {@code actionStrip} is {@code null}
+         */
+        @NonNull
+        public Builder setActionStrip(@NonNull ActionStrip actionStrip) {
+            ACTIONS_CONSTRAINTS_SIMPLE.validateOrThrow(requireNonNull(actionStrip).getActions());
+            mActionStrip = actionStrip;
+            return this;
+        }
+
+        /**
+         * Adds an {@link Action} to display alongside the sign-in content.
+         *
+         * <p>By default, no actions are displayed.
+         *
+         * <h4>Requirements</h4>
+         *
+         * This template allows up to 2 {@link Action}s.
+         *
+         * @throws NullPointerException  if {@code action} is {@code null}
+         * @throws IllegalStateException if more than two actions have been added.
+         */
+        @NonNull
+        public Builder addAction(@NonNull Action action) {
+            if (mActionList.size() >= MAX_ACTIONS_ALLOWED) {
+                throw new IllegalStateException(
+                        "This template allows only up to " + MAX_ACTIONS_ALLOWED + " actions");
+            }
+            requireNonNull(action);
+            mActionList.add(action);
+            return this;
+        }
+
+        /**
+         * Sets the title of the template.
+         *
+         * <p>Unless set with this method, the template will not have a title.
+         *
+         * <p>Spans are not supported in the input string.
+         *
+         * @throws NullPointerException if {@code title} is {@code null}
+         */
+        @NonNull
+        public Builder setTitle(@NonNull CharSequence title) {
+            mTitle = CarText.create(requireNonNull(title));
+            return this;
+        }
+
+        /**
+         * Sets the text to show as instructions of the template.
+         *
+         * <p>Unless set with this method, the template will not have instructions.
+         *
+         * <p>Spans are supported in the input string.
+         *
+         * @throws NullPointerException if {@code instructions} is {@code null}
+         * @see CarText for details on text handling and span support.
+         */
+        // TODO(b/181569051): document supported span types.
+        @NonNull
+        public Builder setInstructions(@NonNull CharSequence instructions) {
+            mInstructions = CarText.create(requireNonNull(instructions));
+            return this;
+        }
+
+        /**
+         * Sets additional text, such as disclaimers, links to terms of services, to show in the
+         * template.
+         *
+         * <p>Unless set with this method, the template will not have additional text.
+         *
+         * <p>Spans are supported in the input string.
+         *
+         * @throws NullPointerException if {@code additionalText} is {@code null}
+         * @see CarText
+         */
+        // TODO(b/181569051): document supported span types.
+        @NonNull
+        public Builder setAdditionalText(@NonNull CharSequence additionalText) {
+            mAdditionalText = CarText.create(requireNonNull(additionalText));
+            return this;
+        }
+
+        /**
+         * Constructs the template defined by this builder.
+         *
+         * <h4>Requirements</h4>
+         *
+         * Either a header {@link Action} or the title must be set.
+         *
+         * @throws IllegalStateException if the template does not have either a title or header
+         *                               {@link Action} set
+         */
+        @NonNull
+        public SignInTemplate build() {
+            if (CarText.isNullOrEmpty(mTitle) && mHeaderAction == null) {
+                throw new IllegalStateException("Either the title or header action must be set");
+            }
+            return new SignInTemplate(this);
+        }
+
+        /**
+         * Returns a {@link Builder} instance.
+         *
+         * @param signInMethod the sign-in method to use in this template
+         * @throws NullPointerException if the {@code signInMethod} is {@code null}
+         */
+        public Builder(@NonNull SignInMethod signInMethod) {
+            mSignInMethod = requireNonNull(signInMethod);
+        }
+    }
+}
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/NavigationManager.java b/car/app/app/src/main/java/androidx/car/app/navigation/NavigationManager.java
index 2ef24a6..c181f0d 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/NavigationManager.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/NavigationManager.java
@@ -67,7 +67,6 @@
     private final INavigationManager.Stub mNavigationManager;
     private final HostDispatcher mHostDispatcher;
 
-
     // Guarded by main thread access.
     @Nullable
     private NavigationManagerCallback mNavigationManagerCallback;
@@ -286,9 +285,14 @@
             return;
         }
         mIsNavigating = false;
-        requireNonNull(mNavigationManagerCallbackExecutor).execute(() -> {
-            requireNonNull(mNavigationManagerCallback).onStopNavigation();
-        });
+
+        NavigationManagerCallback callback = mNavigationManagerCallback;
+        Executor executor = mNavigationManagerCallbackExecutor;
+        if (callback == null || executor == null) {
+            return;
+        }
+
+        executor.execute(callback::onStopNavigation);
     }
 
     /**
@@ -309,14 +313,16 @@
         }
 
         mIsAutoDriveEnabled = true;
+
         NavigationManagerCallback callback = mNavigationManagerCallback;
-        if (callback != null) {
-            requireNonNull(mNavigationManagerCallbackExecutor).execute(
-                    callback::onAutoDriveEnabled);
-        } else {
+        Executor executor = mNavigationManagerCallbackExecutor;
+        if (callback == null || executor == null) {
             Log.w(TAG_NAVIGATION_MANAGER,
                     "NavigationManagerCallback not set, skipping onAutoDriveEnabled");
+            return;
         }
+
+        executor.execute(callback::onAutoDriveEnabled);
     }
 
     /** @hide */
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/MessageInfo.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/MessageInfo.java
index 2fa728d..6804b46 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/MessageInfo.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/MessageInfo.java
@@ -21,6 +21,7 @@
 import androidx.annotation.Keep;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.car.app.annotations.ExperimentalCarApi;
 import androidx.car.app.model.CarIcon;
 import androidx.car.app.model.CarText;
 import androidx.car.app.model.constraints.CarIconConstraints;
@@ -121,8 +122,6 @@
         /**
          * Sets the title of the message.
          *
-         * <p>Unless set with this method, the message will not have a title.
-         *
          * <p>Spans are not supported in the input string.
          *
          * @throws NullPointerException if {@code message} is {@code null}
@@ -137,8 +136,6 @@
         /**
          * Sets additional text on the message.
          *
-         * <p>Unless set with this method, the message will not have additional text.
-         *
          * <p>Spans are not supported in the input string.
          *
          * @throws NullPointerException if {@code text} is {@code null}
@@ -151,6 +148,21 @@
         }
 
         /**
+         * Sets additional text on the message.
+         *
+         * <p>Spans are not supported in the input string.
+         *
+         * @throws NullPointerException if {@code text} is {@code null}
+         * @see CarText
+         */
+        @ExperimentalCarApi
+        @NonNull
+        public Builder setText(@NonNull CarText text) {
+            mText = requireNonNull(text);
+            return this;
+        }
+
+        /**
          * Sets the image to display along with the message.
          *
          * <p>Unless set with this method, the message will not have an image.
@@ -178,5 +190,15 @@
         public Builder(@NonNull CharSequence title) {
             mTitle = CarText.create(requireNonNull(title));
         }
+
+        /**
+         * Returns a new instance of a {@link Builder}.
+         *
+         * @throws NullPointerException if {@code title} is {@code null}
+         */
+        @ExperimentalCarApi
+        public Builder(@NonNull CarText title) {
+            mTitle = requireNonNull(title);
+        }
     }
 }
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/Step.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/Step.java
index a4b0a4b..731bf2e 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/Step.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/Step.java
@@ -21,6 +21,7 @@
 import androidx.annotation.Keep;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.car.app.annotations.ExperimentalCarApi;
 import androidx.car.app.model.CarIcon;
 import androidx.car.app.model.CarText;
 import androidx.car.app.utils.CollectionUtils;
@@ -192,6 +193,17 @@
         }
 
         /**
+         * Constructs a new builder of {@link Step} with a cue.
+         *
+         * @throws NullPointerException if {@code cue} is {@code null}
+         * @see Builder#Builder(CharSequence)
+         */
+        @ExperimentalCarApi
+        public Builder(@NonNull CarText cue) {
+            mCue = requireNonNull(cue);
+        }
+
+        /**
          * Sets the maneuver to be performed on this step.
          *
          * @throws NullPointerException if {@code maneuver} is {@code null}
diff --git a/car/app/app/src/test/java/androidx/car/app/model/ActionTest.java b/car/app/app/src/test/java/androidx/car/app/model/ActionTest.java
index cbd07ee..9b2303b 100644
--- a/car/app/app/src/test/java/androidx/car/app/model/ActionTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/ActionTest.java
@@ -105,6 +105,17 @@
     }
 
     @Test
+    public void create_titleHasTextVariants() {
+        CarText title = new CarText.Builder("foo long text").addVariant("foo").build();
+        OnClickListener onClickListener = mock(OnClickListener.class);
+        Action action = new Action.Builder().setTitle(title).setOnClickListener(
+                onClickListener).build();
+        assertThat(action.getTitle()).isNotNull();
+        assertThat(action.getTitle().toCharSequence().toString()).isEqualTo("foo long text");
+        assertThat(action.getTitle().getVariants().get(0).toString()).isEqualTo("foo");
+    }
+
+    @Test
     public void create_noBackgroundColorDefault() {
         OnClickListener onClickListener = mock(OnClickListener.class);
         Action action = new Action.Builder().setTitle("foo").setOnClickListener(
diff --git a/car/app/app/src/test/java/androidx/car/app/model/CarTextTest.java b/car/app/app/src/test/java/androidx/car/app/model/CarTextTest.java
index 37092cb..7e34582 100644
--- a/car/app/app/src/test/java/androidx/car/app/model/CarTextTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/CarTextTest.java
@@ -87,6 +87,75 @@
     }
 
     @Test
+    public void variants_toCharSequence_withSpans() {
+        String text1 = "Part of this text is red";
+        SpannableString spannable1 = new SpannableString(text1);
+        ForegroundCarColorSpan foregroundCarColorSpan1 =
+                ForegroundCarColorSpan.create(CarColor.RED);
+        spannable1.setSpan(foregroundCarColorSpan1, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        DurationSpan durationSpan1 = DurationSpan.create(46);
+        spannable1.setSpan(durationSpan1, 10, 12, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        // Create a text where the string is different
+        String text2 = "Part of this text is blue";
+        SpannableString spannable2 = new SpannableString(text2);
+        ForegroundCarColorSpan foregroundCarColorSpan2 =
+                ForegroundCarColorSpan.create(CarColor.RED);
+        spannable2.setSpan(foregroundCarColorSpan2, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        DurationSpan durationSpan2 = DurationSpan.create(46);
+        spannable2.setSpan(durationSpan2, 10, 12, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        // Create the car text from the spannables and verify it.
+        CarText carText = new CarText.Builder(spannable1).addVariant(spannable2).build();
+
+        // Check that we have two variants.
+        assertThat(carText.toCharSequence()).isNotNull();
+        assertThat(carText.getVariants()).hasSize(1);
+
+        // Check the first variant.
+        CharSequence charSequence1 = carText.toCharSequence();
+        assertThat(charSequence1.toString()).isEqualTo(text1);
+
+        List<CarSpanInfo> carSpans1 = getCarSpans(charSequence1);
+        assertThat(carSpans1).hasSize(2);
+
+        CarSpanInfo carSpan = carSpans1.get(0);
+        assertThat(carSpan.mCarSpan instanceof ForegroundCarColorSpan).isTrue();
+        assertThat(carSpan.mCarSpan).isEqualTo(foregroundCarColorSpan1);
+        assertThat(carSpan.mStart).isEqualTo(0);
+        assertThat(carSpan.mEnd).isEqualTo(5);
+        assertThat(carSpan.mFlags).isEqualTo(Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        carSpan = carSpans1.get(1);
+        assertThat(carSpan.mCarSpan instanceof DurationSpan).isTrue();
+        assertThat(carSpan.mCarSpan).isEqualTo(durationSpan1);
+        assertThat(carSpan.mStart).isEqualTo(10);
+        assertThat(carSpan.mEnd).isEqualTo(12);
+        assertThat(carSpan.mFlags).isEqualTo(Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        // Check the second variant.
+        CharSequence charSequence2 = carText.getVariants().get(0);
+        assertThat(charSequence2.toString()).isEqualTo(text2);
+
+        List<CarSpanInfo> carSpans = getCarSpans(charSequence2);
+        assertThat(carSpans).hasSize(2);
+
+        carSpan = carSpans.get(0);
+        assertThat(carSpan.mCarSpan instanceof ForegroundCarColorSpan).isTrue();
+        assertThat(carSpan.mCarSpan).isEqualTo(foregroundCarColorSpan2);
+        assertThat(carSpan.mStart).isEqualTo(0);
+        assertThat(carSpan.mEnd).isEqualTo(5);
+        assertThat(carSpan.mFlags).isEqualTo(Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        carSpan = carSpans.get(1);
+        assertThat(carSpan.mCarSpan instanceof DurationSpan).isTrue();
+        assertThat(carSpan.mCarSpan).isEqualTo(durationSpan2);
+        assertThat(carSpan.mStart).isEqualTo(10);
+        assertThat(carSpan.mEnd).isEqualTo(12);
+        assertThat(carSpan.mFlags).isEqualTo(Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+    }
+
+    @Test
     public void equals_and_hashCode() {
         String text = "Part of this text is red";
         SpannableString spannable = new SpannableString(text);
diff --git a/car/app/app/src/test/java/androidx/car/app/model/GridItemTest.java b/car/app/app/src/test/java/androidx/car/app/model/GridItemTest.java
index d46b6ad..3c4c45f 100644
--- a/car/app/app/src/test/java/androidx/car/app/model/GridItemTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/GridItemTest.java
@@ -64,6 +64,15 @@
     }
 
     @Test
+    public void title_variants() {
+        CarText title = new CarText.Builder("Foo Long").addVariant("Foo").build();
+        GridItem gridItem = new GridItem.Builder().setTitle(title).setImage(BACK).build();
+
+        assertThat(gridItem.getTitle().toString()).isEqualTo("Foo Long");
+        assertThat(gridItem.getTitle().getVariants().get(0).toString()).isEqualTo("Foo");
+    }
+
+    @Test
     public void title_throwsIfNotSet() {
         // Not set
         assertThrows(IllegalStateException.class,
@@ -85,6 +94,16 @@
     }
 
     @Test
+    public void text_variants() {
+        CarText text = new CarText.Builder("Foo Long").addVariant("Foo").build();
+        GridItem gridItem = new GridItem.Builder().setTitle("title").setText(text).setImage(
+                BACK).build();
+
+        assertThat(gridItem.getText().toString()).isEqualTo("Foo Long");
+        assertThat(gridItem.getText().getVariants().get(0).toString()).isEqualTo("Foo");
+    }
+
+    @Test
     public void textWithoutTitle_throws() {
         assertThrows(
                 IllegalStateException.class,
diff --git a/car/app/app/src/test/java/androidx/car/app/model/MessageTemplateTest.java b/car/app/app/src/test/java/androidx/car/app/model/MessageTemplateTest.java
index 85d161e..515daca 100644
--- a/car/app/app/src/test/java/androidx/car/app/model/MessageTemplateTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/MessageTemplateTest.java
@@ -33,6 +33,9 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.internal.DoNotInstrument;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /** Tests for {@link MessageTemplate}. */
 @RunWith(RobolectricTestRunner.class)
 @DoNotInstrument
@@ -124,6 +127,25 @@
     }
 
     @Test
+    public void create_messageHasTextVariants() {
+        List<CharSequence> variants = new ArrayList<>();
+        variants.add("This is a long message that only fits in a large screen");
+        variants.add("This is a short message");
+        CarText message =
+                new CarText.Builder(variants.get(0)).addVariant(variants.get(1)).build();
+
+        MessageTemplate template =
+                new MessageTemplate.Builder(message)
+                        .setTitle(mTitle)
+                        .build();
+
+        assertThat(template.getMessage().toCharSequence().toString()).isEqualTo(variants.get(0));
+        assertThat(template.getMessage().getVariants().size()).isEqualTo(1);
+        assertThat(template.getMessage().getVariants().get(0).toString()).isEqualTo(
+                variants.get(1));
+    }
+
+    @Test
     public void equals() {
         MessageTemplate template1 =
                 new MessageTemplate.Builder(mMessage)
diff --git a/car/app/app/src/test/java/androidx/car/app/model/RowTest.java b/car/app/app/src/test/java/androidx/car/app/model/RowTest.java
index d459e89..35d82dd 100644
--- a/car/app/app/src/test/java/androidx/car/app/model/RowTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/model/RowTest.java
@@ -34,6 +34,9 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.internal.DoNotInstrument;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /** Tests for {@link Row}. */
 @RunWith(RobolectricTestRunner.class)
 @DoNotInstrument
@@ -66,6 +69,25 @@
     }
 
     @Test
+    public void title_text_variants() {
+        List<CharSequence> titleVariants = new ArrayList<>();
+        titleVariants.add("foo");
+        titleVariants.add("foo long");
+
+        List<CharSequence> textVariants = new ArrayList<>();
+        textVariants.add("bar");
+        textVariants.add("bar long");
+
+        CarText title =
+                new CarText.Builder(titleVariants.get(0)).addVariant(titleVariants.get(1)).build();
+        CarText text = new CarText.Builder(textVariants.get(0)).addVariant(
+                textVariants.get(1)).build();
+        Row row = new Row.Builder().setTitle(title).addText(text).build();
+        assertThat(title).isEqualTo(row.getTitle());
+        assertThat(row.getTexts()).containsExactly(text);
+    }
+
+    @Test
     public void setImage() {
         CarIcon image1 = BACK;
         Row row = new Row.Builder().setTitle("Title").setImage(image1).build();
diff --git a/car/app/app/src/test/java/androidx/car/app/model/signin/InputSignInMethodTest.java b/car/app/app/src/test/java/androidx/car/app/model/signin/InputSignInMethodTest.java
new file mode 100644
index 0000000..142424b
--- /dev/null
+++ b/car/app/app/src/test/java/androidx/car/app/model/signin/InputSignInMethodTest.java
@@ -0,0 +1,258 @@
+/*
+ * 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.car.app.model.signin;
+
+import static androidx.car.app.model.signin.InputSignInMethod.INPUT_TYPE_DEFAULT;
+import static androidx.car.app.model.signin.InputSignInMethod.INPUT_TYPE_PASSWORD;
+import static androidx.car.app.model.signin.InputSignInMethod.KEYBOARD_DEFAULT;
+import static androidx.car.app.model.signin.InputSignInMethod.KEYBOARD_EMAIL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import androidx.car.app.OnDoneCallback;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+/** Tests for {@link InputSignInMethod}. */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class InputSignInMethodTest {
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    InputSignInMethod.OnInputCompletedListener mListener;
+
+    @Test
+    public void create_defaultValues() {
+        InputSignInMethod signIn = new InputSignInMethod.Builder(mListener).build();
+
+        assertThat(signIn.getInputType()).isEqualTo(INPUT_TYPE_DEFAULT);
+        assertThat(signIn.getKeyboardType()).isEqualTo(KEYBOARD_DEFAULT);
+        assertThat(signIn.getPrompt()).isNull();
+        assertThat(signIn.getMessage()).isNull();
+        assertThat(signIn.isShowKeyboardByDefault()).isFalse();
+
+        OnInputCompletedDelegate delegate = signIn.getOnInputCompletedDelegate();
+        OnDoneCallback onDoneCallback = mock(OnDoneCallback.class);
+        delegate.sendInputCompleted("ABC", onDoneCallback);
+
+        verify(mListener).onInputCompleted("ABC");
+        verify(onDoneCallback).onSuccess(null);
+    }
+
+    @Test
+    public void create_withInputType() {
+        InputSignInMethod signIn = new InputSignInMethod.Builder(mListener)
+                .setInputType(INPUT_TYPE_PASSWORD)
+                .build();
+
+        assertThat(signIn.getInputType()).isEqualTo(INPUT_TYPE_PASSWORD);
+    }
+
+    @Test
+    public void create_withKeyboardType() {
+        InputSignInMethod signIn = new InputSignInMethod.Builder(mListener)
+                .setKeyboardType(KEYBOARD_EMAIL)
+                .build();
+
+        assertThat(signIn.getKeyboardType()).isEqualTo(KEYBOARD_EMAIL);
+    }
+
+    @Test
+    public void create_wtihPrompt() {
+        InputSignInMethod signIn = new InputSignInMethod.Builder(mListener)
+                .setPrompt("Signin")
+                .build();
+
+        assertThat(signIn.getPrompt().toString()).isEqualTo("Signin");
+    }
+
+    @Test
+    public void create_withMessage() {
+        InputSignInMethod signIn = new InputSignInMethod.Builder(mListener)
+                .setMessage("error")
+                .build();
+
+        assertThat(signIn.getMessage().toString()).isEqualTo("error");
+    }
+
+    @Test
+    public void create_showKeyboard() {
+        InputSignInMethod signIn = new InputSignInMethod.Builder(mListener)
+                .setShowKeyboardByDefault(true)
+                .build();
+
+        assertThat(signIn.isShowKeyboardByDefault()).isTrue();
+    }
+
+    @Test
+    public void equals() {
+        int inputType = INPUT_TYPE_PASSWORD;
+        int keyboardType = KEYBOARD_EMAIL;
+        String message = "error";
+        String instructions = "sign";
+
+        InputSignInMethod signIn = new InputSignInMethod.Builder(mListener)
+                .setInputType(inputType)
+                .setKeyboardType(keyboardType)
+                .setPrompt(instructions)
+                .setMessage(message)
+                .setShowKeyboardByDefault(true)
+                .build();
+
+        assertThat(signIn)
+                .isEqualTo(new InputSignInMethod.Builder(mListener)
+                        .setInputType(inputType)
+                        .setKeyboardType(keyboardType)
+                        .setPrompt(instructions)
+                        .setMessage(message)
+                        .setShowKeyboardByDefault(true)
+                        .build());
+    }
+
+    @Test
+    public void notEquals_differentInputType() {
+        int keyboardType = KEYBOARD_EMAIL;
+        String message = "error";
+        String instructions = "sign";
+
+        InputSignInMethod signIn = new InputSignInMethod.Builder(mListener)
+                .setInputType(INPUT_TYPE_PASSWORD)
+                .setKeyboardType(keyboardType)
+                .setPrompt(instructions)
+                .setMessage(message)
+                .setShowKeyboardByDefault(true)
+                .build();
+
+        assertThat(signIn)
+                .isNotEqualTo(new InputSignInMethod.Builder(mListener)
+                        .setInputType(INPUT_TYPE_DEFAULT)
+                        .setKeyboardType(keyboardType)
+                        .setPrompt(instructions)
+                        .setMessage(message)
+                        .setShowKeyboardByDefault(true)
+                        .build());
+    }
+
+    @Test
+    public void notEquals_differentKeyboardType() {
+        int inputType = INPUT_TYPE_PASSWORD;
+        String message = "error";
+        String instructions = "sign";
+
+        InputSignInMethod signIn = new InputSignInMethod.Builder(mListener)
+                .setInputType(inputType)
+                .setKeyboardType(KEYBOARD_EMAIL)
+                .setPrompt(instructions)
+                .setMessage(message)
+                .setShowKeyboardByDefault(true)
+                .build();
+
+        assertThat(signIn)
+                .isNotEqualTo(new InputSignInMethod.Builder(mListener)
+                        .setInputType(inputType)
+                        .setKeyboardType(KEYBOARD_DEFAULT)
+                        .setPrompt(instructions)
+                        .setMessage(message)
+                        .setShowKeyboardByDefault(true)
+                        .build());
+    }
+
+    @Test
+    public void notEquals_differentInstructions() {
+        int inputType = INPUT_TYPE_PASSWORD;
+        int keyboardType = KEYBOARD_EMAIL;
+        String message = "error";
+
+        InputSignInMethod signIn = new InputSignInMethod.Builder(mListener)
+                .setInputType(inputType)
+                .setKeyboardType(keyboardType)
+                .setPrompt("signin")
+                .setMessage(message)
+                .setShowKeyboardByDefault(true)
+                .build();
+
+        assertThat(signIn)
+                .isNotEqualTo(new InputSignInMethod.Builder(mListener)
+                        .setInputType(inputType)
+                        .setKeyboardType(keyboardType)
+                        .setPrompt("sign2")
+                        .setMessage(message)
+                        .setShowKeyboardByDefault(true)
+                        .build());
+    }
+
+    @Test
+    public void notEquals_differentMessage() {
+        int inputType = INPUT_TYPE_PASSWORD;
+        int keyboardType = KEYBOARD_EMAIL;
+        String instructions = "sign";
+
+        InputSignInMethod signIn = new InputSignInMethod.Builder(mListener)
+                .setInputType(inputType)
+                .setKeyboardType(keyboardType)
+                .setPrompt(instructions)
+                .setMessage("error")
+                .setShowKeyboardByDefault(true)
+                .build();
+
+        assertThat(signIn)
+                .isNotEqualTo(new InputSignInMethod.Builder(mListener)
+                        .setInputType(inputType)
+                        .setKeyboardType(keyboardType)
+                        .setPrompt(instructions)
+                        .setMessage("error2")
+                        .setShowKeyboardByDefault(true)
+                        .build());
+    }
+
+    @Test
+    public void notEquals_differentShowKeyboard() {
+        int inputType = INPUT_TYPE_PASSWORD;
+        int keyboardType = KEYBOARD_EMAIL;
+        String message = "error";
+        String instructions = "sign";
+
+        InputSignInMethod signIn = new InputSignInMethod.Builder(mListener)
+                .setInputType(inputType)
+                .setKeyboardType(keyboardType)
+                .setPrompt(instructions)
+                .setMessage(message)
+                .setShowKeyboardByDefault(true)
+                .build();
+
+        assertThat(signIn)
+                .isNotEqualTo(new InputSignInMethod.Builder(mListener)
+                        .setInputType(inputType)
+                        .setKeyboardType(keyboardType)
+                        .setPrompt(instructions)
+                        .setMessage(message)
+                        .setShowKeyboardByDefault(false)
+                        .build());
+    }
+}
diff --git a/car/app/app/src/test/java/androidx/car/app/model/signin/PinSignInMethodTest.java b/car/app/app/src/test/java/androidx/car/app/model/signin/PinSignInMethodTest.java
new file mode 100644
index 0000000..f1b5de1
--- /dev/null
+++ b/car/app/app/src/test/java/androidx/car/app/model/signin/PinSignInMethodTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.car.app.model.signin;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+/** Tests for {@link PinSignInMethod}. */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class PinSignInMethodTest {
+    @Test
+    public void create_emptyPin_throws() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new PinSignInMethod.Builder(""));
+    }
+
+    @Test
+    public void create_defaultValues() {
+        PinSignInMethod signIn = new PinSignInMethod.Builder("ABC").build();
+
+        assertThat(signIn.getPin()).isEqualTo("ABC");
+    }
+
+    @Test
+    public void equals() {
+        String pin = "ABC";
+        PinSignInMethod signIn = new PinSignInMethod.Builder(pin).build();
+        assertThat(signIn).isEqualTo(new PinSignInMethod.Builder(pin).build());
+    }
+
+    @Test
+    public void notEquals_differentPin() {
+        PinSignInMethod signIn = new PinSignInMethod.Builder("ABC").build();
+        assertThat(signIn).isNotEqualTo(new PinSignInMethod.Builder("DEF").build());
+    }
+}
diff --git a/car/app/app/src/test/java/androidx/car/app/model/signin/ProviderSignInMethodTest.java b/car/app/app/src/test/java/androidx/car/app/model/signin/ProviderSignInMethodTest.java
new file mode 100644
index 0000000..c930acd
--- /dev/null
+++ b/car/app/app/src/test/java/androidx/car/app/model/signin/ProviderSignInMethodTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.car.app.model.signin;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
+
+import androidx.car.app.model.Action;
+import androidx.car.app.model.OnClickListener;
+import androidx.car.app.model.ParkedOnlyOnClickListener;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+/** Tests for {@link ProviderSignInMethod}. */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class ProviderSignInMethodTest {
+    @Test
+    public void create_defaultValues() {
+        OnClickListener clickListener = mock(OnClickListener.class);
+        Action action = new Action.Builder()
+                .setTitle("Signin")
+                .setOnClickListener(ParkedOnlyOnClickListener.create(clickListener))
+                .build();
+        ProviderSignInMethod signIn = new ProviderSignInMethod.Builder(action).build();
+
+        assertThat(signIn.getAction()).isEqualTo(action);
+    }
+
+    @Test
+    public void create_standardAction_throws() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new ProviderSignInMethod.Builder(Action.APP_ICON));
+    }
+
+    @Test
+    public void create_nonParkedListener_throws() {
+        OnClickListener clickListener = mock(OnClickListener.class);
+        Action action = new Action.Builder()
+                .setTitle("Signin")
+                .setOnClickListener(clickListener)
+                .build();
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new ProviderSignInMethod.Builder(action));
+    }
+
+    @Test
+    public void equals() {
+        OnClickListener clickListener = mock(OnClickListener.class);
+        Action action = new Action.Builder()
+                .setTitle("Signin")
+                .setOnClickListener(ParkedOnlyOnClickListener.create(clickListener))
+                .build();
+        ProviderSignInMethod signIn = new ProviderSignInMethod.Builder(action).build();
+
+        assertThat(signIn).isEqualTo(new ProviderSignInMethod.Builder(action).build());
+    }
+
+    @Test
+    public void notEquals_differentAction() {
+        OnClickListener clickListener = mock(OnClickListener.class);
+        Action action = new Action.Builder()
+                .setTitle("Signin")
+                .setOnClickListener(ParkedOnlyOnClickListener.create(clickListener))
+                .build();
+        ProviderSignInMethod signIn = new ProviderSignInMethod.Builder(action).build();
+
+        Action action2 = new Action.Builder()
+                .setTitle("Signin2")
+                .setOnClickListener(ParkedOnlyOnClickListener.create(clickListener))
+                .build();
+        assertThat(signIn).isNotEqualTo(new ProviderSignInMethod.Builder(action2).build());
+    }
+}
diff --git a/car/app/app/src/test/java/androidx/car/app/model/signin/SignInTemplateTest.java b/car/app/app/src/test/java/androidx/car/app/model/signin/SignInTemplateTest.java
new file mode 100644
index 0000000..b3aacd6
--- /dev/null
+++ b/car/app/app/src/test/java/androidx/car/app/model/signin/SignInTemplateTest.java
@@ -0,0 +1,332 @@
+/*
+ * 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.car.app.model.signin;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+/** Tests for {@link SignInTemplate}. */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class SignInTemplateTest {
+    @Test
+    public void createInstance_noHeaderTitleOrAction_throws() {
+        PinSignInMethod signInMethod = new PinSignInMethod.Builder("ABC").build();
+        assertThrows(IllegalStateException.class,
+                () -> new SignInTemplate.Builder(signInMethod).build());
+
+        // Positive cases.
+        new SignInTemplate.Builder(signInMethod).setTitle("Title").build();
+        new SignInTemplate.Builder(signInMethod).setHeaderAction(Action.BACK).build();
+    }
+
+    @Test
+    public void createInstance_defaultValues() {
+        PinSignInMethod signInMethod = new PinSignInMethod.Builder("ABC").build();
+        SignInTemplate template = new SignInTemplate.Builder(signInMethod)
+                .setTitle("Title")
+                .build();
+
+        assertThat(template.getTitle().toString()).isEqualTo("Title");
+        assertThat(template.getHeaderAction()).isNull();
+        assertThat(template.getSignInMethod()).isEqualTo(signInMethod);
+        assertThat(template.getActions()).isEmpty();
+        assertThat(template.getActionStrip()).isNull();
+        assertThat(template.getInstructions()).isNull();
+        assertThat(template.getAdditionalText()).isNull();
+    }
+
+    @Test
+    public void createInstance_setHeaderAction_invalidActionThrows() {
+        PinSignInMethod signInMethod = new PinSignInMethod.Builder("ABC").build();
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->
+                        new SignInTemplate.Builder(signInMethod)
+                                .setHeaderAction(
+                                        new Action.Builder().setTitle("Action").setOnClickListener(
+                                                () -> {
+                                                }).build()));
+    }
+
+    @Test
+    public void createInstance_setHeaderAction() {
+        PinSignInMethod signInMethod = new PinSignInMethod.Builder("ABC").build();
+        SignInTemplate template = new SignInTemplate.Builder(signInMethod)
+                .setHeaderAction(Action.BACK)
+                .build();
+        assertThat(template.getHeaderAction()).isEqualTo(Action.BACK);
+    }
+
+    @Test
+    public void createInstance_setActionStrip() {
+        ActionStrip actionStrip = new ActionStrip.Builder().addAction(Action.BACK).build();
+        PinSignInMethod signInMethod = new PinSignInMethod.Builder("ABC").build();
+        SignInTemplate template = new SignInTemplate.Builder(signInMethod)
+                .setTitle("Title")
+                .setActionStrip(actionStrip)
+                .build();
+
+        assertThat(template.getActionStrip()).isEqualTo(actionStrip);
+    }
+
+    @Test
+    public void createInstance_setInstructions() {
+        PinSignInMethod signInMethod = new PinSignInMethod.Builder("ABC").build();
+        SignInTemplate template = new SignInTemplate.Builder(signInMethod)
+                .setTitle("Title")
+                .setInstructions("Text")
+                .build();
+
+        assertThat(template.getInstructions().toString()).isEqualTo("Text");
+    }
+
+    @Test
+    public void createInstance_setAdditionalText() {
+        PinSignInMethod signInMethod = new PinSignInMethod.Builder("ABC").build();
+        SignInTemplate template = new SignInTemplate.Builder(signInMethod)
+                .setTitle("Title")
+                .setAdditionalText("Text")
+                .build();
+
+        assertThat(template.getAdditionalText().toString()).isEqualTo("Text");
+    }
+
+    @Test
+    public void createInstance_addActions() {
+        Action action1 = new Action.Builder().setTitle("Action").build();
+        Action action2 = new Action.Builder().setTitle("Action").build();
+        PinSignInMethod signInMethod = new PinSignInMethod.Builder("ABC").build();
+        SignInTemplate template = new SignInTemplate.Builder(signInMethod)
+                .setTitle("Title")
+                .addAction(action1)
+                .addAction(action2)
+                .build();
+
+        assertThat(template.getActions()).containsExactly(action1, action2);
+    }
+
+    @Test
+    public void createInstance_moreThanTwoActions_throws() {
+        Action action = new Action.Builder().setTitle("Action").build();
+        PinSignInMethod signInMethod = new PinSignInMethod.Builder("ABC").build();
+        assertThrows(IllegalStateException.class,
+                () -> new SignInTemplate.Builder(signInMethod)
+                        .setTitle("Title")
+                        .addAction(action)
+                        .addAction(action)
+                        .addAction(action));
+    }
+
+    @Test
+    public void equals() {
+        PinSignInMethod signInMethod = new PinSignInMethod.Builder("ABC").build();
+        String title = "Title";
+        String instructions = "instructions";
+        String additionalText = "Text";
+        Action action = Action.BACK;
+        ActionStrip actionStrip = new ActionStrip.Builder().addAction(action).build();
+
+        SignInTemplate template = new SignInTemplate.Builder(signInMethod)
+                .setTitle(title)
+                .setInstructions(instructions)
+                .setAdditionalText(additionalText)
+                .addAction(action)
+                .setActionStrip(actionStrip)
+                .build();
+
+        assertThat(template)
+                .isEqualTo(
+                        new SignInTemplate.Builder(signInMethod)
+                                .setTitle(title)
+                                .setInstructions(instructions)
+                                .setAdditionalText(additionalText)
+                                .addAction(action)
+                                .setActionStrip(actionStrip)
+                                .build());
+    }
+
+    @Test
+    public void notEquals_differentSignInMethod() {
+        PinSignInMethod signInMethod = new PinSignInMethod.Builder("ABC").build();
+        String title = "Title";
+        String instructions = "instructions";
+        String additionalText = "Text";
+        Action action = Action.BACK;
+        ActionStrip actionStrip = new ActionStrip.Builder().addAction(action).build();
+
+        SignInTemplate template = new SignInTemplate.Builder(signInMethod)
+                .setTitle(title)
+                .setInstructions(instructions)
+                .setAdditionalText(additionalText)
+                .addAction(action)
+                .setActionStrip(actionStrip)
+                .build();
+
+        PinSignInMethod signInMethod2 = new PinSignInMethod.Builder("DEF").build();
+        assertThat(template)
+                .isNotEqualTo(
+                        new SignInTemplate.Builder(signInMethod2)
+                                .setTitle(title)
+                                .setInstructions(instructions)
+                                .setAdditionalText(additionalText)
+                                .addAction(action)
+                                .setActionStrip(actionStrip)
+                                .build());
+    }
+
+    @Test
+    public void notEquals_differentTitle() {
+        PinSignInMethod signInMethod = new PinSignInMethod.Builder("ABC").build();
+        String instructions = "instructions";
+        String additionalText = "Text";
+        Action action = Action.BACK;
+        ActionStrip actionStrip = new ActionStrip.Builder().addAction(action).build();
+
+        SignInTemplate template = new SignInTemplate.Builder(signInMethod)
+                .setTitle("Title")
+                .setInstructions(instructions)
+                .setAdditionalText(additionalText)
+                .addAction(action)
+                .setActionStrip(actionStrip)
+                .build();
+        assertThat(template)
+                .isNotEqualTo(
+                        new SignInTemplate.Builder(signInMethod)
+                                .setTitle("Title2")
+                                .setInstructions(instructions)
+                                .setAdditionalText(additionalText)
+                                .addAction(action)
+                                .setActionStrip(actionStrip)
+                                .build());
+    }
+
+    @Test
+    public void notEquals_differentInstructions() {
+        PinSignInMethod signInMethod = new PinSignInMethod.Builder("ABC").build();
+        String title = "Title";
+        String additionalText = "Text";
+        Action action = Action.BACK;
+        ActionStrip actionStrip = new ActionStrip.Builder().addAction(action).build();
+
+        SignInTemplate template = new SignInTemplate.Builder(signInMethod)
+                .setTitle(title)
+                .setInstructions("instructions1")
+                .setAdditionalText(additionalText)
+                .addAction(action)
+                .setActionStrip(actionStrip)
+                .build();
+        assertThat(template)
+                .isNotEqualTo(
+                        new SignInTemplate.Builder(signInMethod)
+                                .setTitle(title)
+                                .setInstructions("instructions2")
+                                .setAdditionalText(additionalText)
+                                .addAction(action)
+                                .setActionStrip(actionStrip)
+                                .build());
+    }
+
+    @Test
+    public void notEquals_differentAdditionalText() {
+        PinSignInMethod signInMethod = new PinSignInMethod.Builder("ABC").build();
+        String instructions = "instructions";
+        String title = "Title";
+        Action action = Action.BACK;
+        ActionStrip actionStrip = new ActionStrip.Builder().addAction(action).build();
+
+        SignInTemplate template = new SignInTemplate.Builder(signInMethod)
+                .setTitle(title)
+                .setInstructions(instructions)
+                .setAdditionalText("Text")
+                .addAction(action)
+                .setActionStrip(actionStrip)
+                .build();
+        assertThat(template)
+                .isNotEqualTo(
+                        new SignInTemplate.Builder(signInMethod)
+                                .setTitle(title)
+                                .setInstructions(instructions)
+                                .setAdditionalText("Text2")
+                                .addAction(action)
+                                .setActionStrip(actionStrip)
+                                .build());
+    }
+
+    @Test
+    public void notEquals_differentAction() {
+        PinSignInMethod signInMethod = new PinSignInMethod.Builder("ABC").build();
+        String instructions = "instructions";
+        String title = "Title";
+        String additionalText = "Text";
+        ActionStrip actionStrip = new ActionStrip.Builder().addAction(Action.BACK).build();
+
+        SignInTemplate template = new SignInTemplate.Builder(signInMethod)
+                .setTitle(title)
+                .setInstructions(instructions)
+                .setAdditionalText(additionalText)
+                .addAction(Action.BACK)
+                .setActionStrip(actionStrip)
+                .build();
+        assertThat(template)
+                .isNotEqualTo(
+                        new SignInTemplate.Builder(signInMethod)
+                                .setTitle(title)
+                                .setInstructions(instructions)
+                                .setAdditionalText(additionalText)
+                                .addAction(Action.APP_ICON)
+                                .setActionStrip(actionStrip)
+                                .build());
+    }
+
+    @Test
+    public void notEquals_differentActionStrip() {
+        PinSignInMethod signInMethod = new PinSignInMethod.Builder("ABC").build();
+        String instructions = "instructions";
+        String title = "Title";
+        String additionalText = "Text";
+        Action action = Action.BACK;
+        ActionStrip actionStrip = new ActionStrip.Builder().addAction(Action.APP_ICON).build();
+
+        SignInTemplate template = new SignInTemplate.Builder(signInMethod)
+                .setTitle(title)
+                .setInstructions(instructions)
+                .setAdditionalText(additionalText)
+                .addAction(action)
+                .setActionStrip(actionStrip)
+                .build();
+        ActionStrip actionStrip2 = new ActionStrip.Builder().addAction(Action.BACK).build();
+        assertThat(template)
+                .isNotEqualTo(
+                        new SignInTemplate.Builder(signInMethod)
+                                .setTitle(title)
+                                .setInstructions(instructions)
+                                .setAdditionalText(additionalText)
+                                .addAction(action)
+                                .setActionStrip(actionStrip2)
+                                .build());
+    }
+}
diff --git a/car/app/app/src/test/java/androidx/car/app/navigation/NavigationManagerTest.java b/car/app/app/src/test/java/androidx/car/app/navigation/NavigationManagerTest.java
index f587a52..8e3b9c9 100644
--- a/car/app/app/src/test/java/androidx/car/app/navigation/NavigationManagerTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/navigation/NavigationManagerTest.java
@@ -218,6 +218,24 @@
     }
 
     @Test
+    public void onStopNavigation_noListener_doesNotThrow() throws RemoteException {
+        InOrder inOrder = inOrder(mMockNavHost, mNavigationListener);
+
+        mNavigationManager.setNavigationManagerCallback(new SynchronousExecutor(),
+                mNavigationListener);
+        mNavigationManager.navigationStarted();
+        inOrder.verify(mMockNavHost).navigationStarted();
+
+        mNavigationManager.onStopNavigation();
+        mNavigationManager.clearNavigationManagerCallback();
+        inOrder.verify(mNavigationListener).onStopNavigation();
+
+        mNavigationManager.getIInterface().onStopNavigation(mock(IOnDoneCallback.class));
+
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
     public void onAutoDriveEnabled_callsListener() {
         mNavigationManager.setNavigationManagerCallback(new SynchronousExecutor(),
                 mNavigationListener);
diff --git a/car/app/app/src/test/java/androidx/car/app/navigation/model/MessageInfoTest.java b/car/app/app/src/test/java/androidx/car/app/navigation/model/MessageInfoTest.java
index 1bdee55..fa379a7 100644
--- a/car/app/app/src/test/java/androidx/car/app/navigation/model/MessageInfoTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/navigation/model/MessageInfoTest.java
@@ -24,6 +24,7 @@
 import android.net.Uri;
 
 import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.CarText;
 import androidx.core.graphics.drawable.IconCompat;
 
 import org.junit.Test;
@@ -70,7 +71,26 @@
 
     @Test
     public void no_message_throws() {
-        assertThrows(NullPointerException.class, () -> new MessageInfo.Builder(null));
+        assertThrows(NullPointerException.class,
+                () -> new MessageInfo.Builder((CharSequence) null));
+    }
+
+    /** Tests construction of a template where title and text have variants. */
+    @Test
+    public void createInstanceWithTextVariants() {
+        CarText title = new CarText.Builder("Message Long").addVariant("Message").build();
+        CarText text = new CarText.Builder("Secondary Long").addVariant("Secondary").build();
+
+        MessageInfo messageInfo =
+                new MessageInfo.Builder(title).setImage(CarIcon.APP_ICON).setText(
+                        text).build();
+        assertThat(messageInfo.getTitle().toString()).isEqualTo("Message Long");
+        assertThat(messageInfo.getTitle().getVariants().get(0).toString()).isEqualTo(
+                "Message");
+        assertThat(messageInfo.getText().toString()).isEqualTo("Secondary Long");
+        assertThat(messageInfo.getText().getVariants().get(0).toString()).isEqualTo(
+                "Secondary");
+        assertThat(messageInfo.getImage()).isEqualTo(CarIcon.APP_ICON);
     }
 
     @Test
diff --git a/car/app/app/src/test/java/androidx/car/app/navigation/model/StepTest.java b/car/app/app/src/test/java/androidx/car/app/navigation/model/StepTest.java
index 64bc37f..1a46ce9 100644
--- a/car/app/app/src/test/java/androidx/car/app/navigation/model/StepTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/navigation/model/StepTest.java
@@ -24,6 +24,7 @@
 import static org.junit.Assert.assertThrows;
 
 import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.CarText;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -62,6 +63,20 @@
     }
 
     @Test
+    public void createInstance_cueHasVariants() {
+        Lane lane = new Lane.Builder().addDirection(
+                LaneDirection.create(SHAPE_SHARP_LEFT, true)).build();
+        CarText cue = new CarText.Builder("Foo Long").addVariant("Foo").build();
+        Step step =
+                new Step.Builder(cue)
+                        .addLane(lane)
+                        .build();
+
+        assertThat(step.getCue().toString()).isEqualTo("Foo Long");
+        assertThat(step.getCue().getVariants().get(0).toString()).isEqualTo("Foo");
+    }
+
+    @Test
     public void createInstance_lanesImage_no_lanes_throws() {
         String cue = "Left at State street.";
 
diff --git a/compose/README.md b/compose/README.md
index 6b2ad38..f50ae06 100644
--- a/compose/README.md
+++ b/compose/README.md
@@ -25,15 +25,16 @@
     cd path/to/checkout/frameworks/support/
     ./gradlew :compose:integration-tests:demos:installDebug
 
-## Currently available components
-Jetpack Compose is in very early stages of development. Developers wanting to build sample apps will probably want to include the material, layout and framework modules. You can see how to setup your dependencies in `material/integration-tests/material-studies/build.gradle`.
-
-Run the `demos` app to see examples of individual components.
-
-A sample implementation of the [Material Rally app](https://material.io/design/material-studies/rally.html) is under `material/integration-tests/material-studies`. This can be viewed from inside the `demos` app, under the 'Material Studies' section.
-
 ## Structure
-Library code for Jetpack Compose lives under the `frameworks/support/compose` directory. Additionally, sample code can be found within each module in the `integration-tests` subdirectories.
+Library code for Jetpack Compose lives under the `frameworks/support/compose` directory. Additionally, sample code can be found within each module in the `integration-tests` subdirectories. Run the `demos` app to see examples of components and behavior.
+
+## Guidance and documentation
+
+[Get started with Jetpack Compose](https://goo.gle/compose-docs)
+
+[Samples](https://goo.gle/compose-samples)
+
+[Pathway course](https://goo.gle/compose-pathway)
 
 ## Feedback
 To provide feedback or report bugs, please refer to the main [AndroidX contribution guide](https://android.googlesource.com/platform/frameworks/support/+/androidx-main/README.md) and report your bugs [here](https://issuetracker.google.com/issues/new?component=612128)
@@ -45,3 +46,5 @@
 [Existing open bugs](https://issuetracker.google.com/issues?q=componentid:612128%20status:open)
 
 [File a new bug](https://issuetracker.google.com/issues/new?component=612128)
+
+[Slack](https://goo.gle/compose-slack)
diff --git a/compose/androidview/androidview/integration-tests/androidview-demos/lint-baseline.xml b/compose/androidview/androidview/integration-tests/androidview-demos/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/androidview/androidview/integration-tests/androidview-demos/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/androidview/androidview/lint-baseline.xml b/compose/androidview/androidview/lint-baseline.xml
index 1538f8f..bf62433 100644
--- a/compose/androidview/androidview/lint-baseline.xml
+++ b/compose/androidview/androidview/lint-baseline.xml
@@ -1,26 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
+<issues format="5" by="lint 4.2.0-beta04" client="gradle" variant="debug" version="4.2.0-beta04">
 
     <issue
         id="BanUncheckedReflection"
diff --git a/compose/animation/animation-core/build.gradle b/compose/animation/animation-core/build.gradle
index cbd4178..ded35c3 100644
--- a/compose/animation/animation-core/build.gradle
+++ b/compose/animation/animation-core/build.gradle
@@ -17,7 +17,7 @@
 
 import androidx.build.AndroidXUiPlugin
 import androidx.build.LibraryGroups
-import androidx.build.Publish
+import androidx.build.LibraryType
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 import static androidx.build.dependencies.DependenciesKt.*
@@ -109,7 +109,7 @@
 
 androidx {
     name = "Compose Animation Core"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.ANIMATION
     inceptionYear = "2019"
     description = "Animation engine and animation primitives that are the building blocks of the Compose animation library"
diff --git a/compose/animation/animation-core/lint-baseline.xml b/compose/animation/animation-core/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/animation/animation-core/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/animation/animation-core/samples/lint-baseline.xml b/compose/animation/animation-core/samples/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/animation/animation-core/samples/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/SingleValueAnimationTest.kt b/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/SingleValueAnimationTest.kt
index a1e44bc..5b05a89 100644
--- a/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/SingleValueAnimationTest.kt
+++ b/compose/animation/animation-core/src/androidAndroidTest/kotlin/androidx/compose/animation/core/SingleValueAnimationTest.kt
@@ -19,10 +19,12 @@
 import androidx.compose.animation.animateColorAsState
 import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.withFrameNanos
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
+import androidx.compose.runtime.withFrameMillis
+import androidx.compose.runtime.withFrameNanos
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.geometry.Size
@@ -248,7 +250,10 @@
                             val playTime = (frameTime - startTime) / 1_000_000L
                             val fraction = FastOutLinearInEasing.transform(playTime / 100f)
                             val expected = lerp(Color.Black, Color.Cyan, fraction)
-                            assertEquals(expected, value)
+                            assertEquals(expected.red, value.red, 1 / 255f)
+                            assertEquals(expected.green, value.green, 1 / 255f)
+                            assertEquals(expected.blue, value.blue, 1 / 255f)
+                            assertEquals(expected.alpha, value.alpha, 1 / 255f)
                             frameTime = withFrameNanos { it }
                         } while (frameTime - startTime <= 100_000_000L)
                     }
@@ -261,6 +266,54 @@
     }
 
     @Test
+    fun frameByFrameInterruptionTest() {
+        var enabled by mutableStateOf(false)
+        var currentValue by mutableStateOf(Offset(-300f, -300f))
+        rule.setContent {
+            Box {
+                var destination: Offset by remember { mutableStateOf(Offset(600f, 600f)) }
+                val offsetValue = animateOffsetAsState(
+                    if (enabled)
+                        destination
+                    else
+                        Offset(0f, 0f)
+                )
+                if (enabled) {
+                    LaunchedEffect(enabled) {
+                        var startTime = -1L
+                        while (true) {
+                            val current = withFrameMillis {
+                                if (startTime < 0) startTime = it
+                                // Fuzzy test by fine adjusting the target on every frame, and
+                                // verify there's a reasonable amount of test. This is to make sure
+                                // the animation does not stay "frozen" when there's continuous
+                                // target changes.
+                                if (destination.x >= 600) {
+                                    destination = Offset(599f, 599f)
+                                } else {
+                                    destination = Offset(601f, 601f)
+                                }
+                                it
+                            }
+                            currentValue = offsetValue.value
+                            if (current - startTime > 1000) {
+                                break
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        rule.runOnIdle {
+            enabled = true
+            assertEquals(Offset(-300f, -300f), currentValue)
+        }
+        rule.waitUntil(1300) {
+            currentValue.x > 300f && currentValue.y > 300f
+        }
+    }
+
+    @Test
     fun visibilityThresholdTest() {
 
         val specForFloat = FloatSpringSpec(visibilityThreshold = 0.01f)
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimateAsState.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimateAsState.kt
index 854be30..ccaf577 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimateAsState.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/AnimateAsState.kt
@@ -28,6 +28,8 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.launch
 
 private val defaultAnimation = spring<Float>()
 
@@ -67,21 +69,13 @@
         } else {
             animationSpec
         }
-    val animationState: AnimationState<Float, AnimationVector1D> = remember {
-        AnimationState(targetValue)
-    }
-
-    val currentEndListener by rememberUpdatedState(finishedListener)
-    LaunchedEffect(targetValue, animationSpec) {
-        animationState.animateTo(
-            targetValue,
-            resolvedAnimSpec,
-            // If the previous animation was interrupted (i.e. not finished), make it sequential.
-            !animationState.isFinished
-        )
-        currentEndListener?.invoke(animationState.value)
-    }
-    return animationState
+    return animateValueAsState(
+        targetValue,
+        Float.VectorConverter,
+        resolvedAnimSpec,
+        visibilityThreshold,
+        finishedListener
+    )
 }
 
 /**
@@ -361,19 +355,26 @@
     visibilityThreshold: T? = null,
     finishedListener: ((T) -> Unit)? = null
 ): State<T> {
-    val animationState: AnimationState<T, V> = remember(typeConverter) {
-        AnimationState(typeConverter, targetValue)
-    }
 
+    val animatable = remember { Animatable(targetValue, typeConverter) }
     val listener by rememberUpdatedState(finishedListener)
-    LaunchedEffect(targetValue, animationSpec) {
-        animationState.animateTo(
-            targetValue,
-            animationSpec,
-            // If the previous animation was interrupted (i.e. not finished), make it sequential.
-            !animationState.isFinished
-        )
-        listener?.invoke(animationState.value)
+    val channel = remember { Channel<T>(Channel.CONFLATED) }
+    channel.offer(targetValue)
+    LaunchedEffect(channel) {
+        for (target in channel) {
+            // This additional poll is needed because when the channel suspends on receive and
+            // two values are produced before consumers' dispatcher resumes, only the first value
+            // will be received.
+            // It may not be an issue elsewhere, but in animation we want to avoid being one
+            // frame late.
+            val newTarget = channel.poll() ?: target
+            launch {
+                if (newTarget != animatable.targetValue) {
+                    animatable.animateTo(newTarget, animationSpec)
+                    listener?.invoke(animatable.value)
+                }
+            }
+        }
     }
-    return animationState
+    return animatable.asState()
 }
\ No newline at end of file
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/SuspendAnimation.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/SuspendAnimation.kt
index 58351f1..a3b9b3a 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/SuspendAnimation.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/SuspendAnimation.kt
@@ -225,28 +225,41 @@
     val initialVelocityVector = animation.getVelocityVectorFromNanos(0)
     var lateInitScope: AnimationScope<T, V>? = null
     try {
-        val startTimeNanosSpecified =
-            if (startTimeNanos == AnimationConstants.UnspecifiedTime) {
-                animation.callWithFrameNanos { it }
-            } else {
-                startTimeNanos
-            }
-        lateInitScope = AnimationScope(
-            initialValue = initialValue,
-            typeConverter = animation.typeConverter,
-            initialVelocityVector = initialVelocityVector,
-            lastFrameTimeNanos = startTimeNanosSpecified,
-            targetValue = animation.targetValue,
-            startTimeNanos = startTimeNanosSpecified,
-            isRunning = true,
-            onCancel = { isRunning = false }
-        )
-        // First frame
-        lateInitScope.doAnimationFrame(startTimeNanosSpecified, animation, this, block)
-        // Subsequent frames
-        while (lateInitScope.isRunning) {
+        if (startTimeNanos == AnimationConstants.UnspecifiedTime) {
             animation.callWithFrameNanos {
-                lateInitScope.doAnimationFrame(it, animation, this, block)
+                lateInitScope = AnimationScope(
+                    initialValue = initialValue,
+                    typeConverter = animation.typeConverter,
+                    initialVelocityVector = initialVelocityVector,
+                    lastFrameTimeNanos = it,
+                    targetValue = animation.targetValue,
+                    startTimeNanos = it,
+                    isRunning = true,
+                    onCancel = { isRunning = false }
+                ).apply {
+                    // First frame
+                    doAnimationFrame(it, animation, this@animate, block)
+                }
+            }
+        } else {
+            lateInitScope = AnimationScope(
+                initialValue = initialValue,
+                typeConverter = animation.typeConverter,
+                initialVelocityVector = initialVelocityVector,
+                lastFrameTimeNanos = startTimeNanos,
+                targetValue = animation.targetValue,
+                startTimeNanos = startTimeNanos,
+                isRunning = true,
+                onCancel = { isRunning = false }
+            ).apply {
+                // First frame
+                doAnimationFrame(startTimeNanos, animation, this@animate, block)
+            }
+        }
+        // Subsequent frames
+        while (lateInitScope!!.isRunning) {
+            animation.callWithFrameNanos {
+                lateInitScope!!.doAnimationFrame(it, animation, this, block)
             }
         }
         // End of animation
diff --git a/compose/animation/animation/build.gradle b/compose/animation/animation/build.gradle
index ceca2a7..e75682a 100644
--- a/compose/animation/animation/build.gradle
+++ b/compose/animation/animation/build.gradle
@@ -17,7 +17,7 @@
 
 import androidx.build.AndroidXUiPlugin
 import androidx.build.LibraryGroups
-import androidx.build.Publish
+import androidx.build.LibraryType
 
 import static androidx.build.dependencies.DependenciesKt.*
 
@@ -109,7 +109,7 @@
 
 androidx {
     name = "Compose Animation"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.ANIMATION
     inceptionYear = "2019"
     description = "Compose animation library"
diff --git a/compose/animation/animation/integration-tests/animation-demos/lint-baseline.xml b/compose/animation/animation/integration-tests/animation-demos/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/animation/animation/integration-tests/animation-demos/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedDotsDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedDotsDemo.kt
new file mode 100644
index 0000000..9d9ce60
--- /dev/null
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedDotsDemo.kt
@@ -0,0 +1,158 @@
+/*
+ * 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.animation.demos
+
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.rememberInfiniteTransition
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.lerp
+import androidx.compose.ui.unit.dp
+import kotlin.math.abs
+import kotlin.math.min
+
+@Composable
+fun AnimatedDotsDemo() {
+    val infiniteTransition = rememberInfiniteTransition()
+    val position by infiniteTransition.animateFloat(
+        initialValue = 1f,
+        targetValue = totalDotCount.toFloat(),
+        animationSpec = infiniteRepeatable(
+            animation = tween(2000),
+            repeatMode = RepeatMode.Reverse
+        )
+    )
+    Dots(position)
+}
+
+private const val totalDotCount = 4
+private const val dotSpacing = 60f
+private const val dotComposableHeight = 200f
+
+@Composable
+private fun Dots(position: Float) {
+    Canvas(modifier = Modifier.size(400.dp, dotComposableHeight.dp)) {
+        val centerY = dotComposableHeight / 2
+        for (currentDotPosition in 1..totalDotCount) {
+            val dotSize = getDotSizeForPosition(position, currentDotPosition)
+            if (currentDotPosition < totalDotCount) {
+                // Draw a bridge between the current dot and the next dot
+                val nextDotPosition = currentDotPosition + 1
+                val nextDotSize = getDotSizeForPosition(position, nextDotPosition)
+                // Pick a direction to draw bridge from the smaller dot to the larger dot
+                val shouldFlip = nextDotSize > dotSize
+                val nextPositionDelta = -min(
+                    1f,
+                    abs(position - if (shouldFlip) nextDotPosition else currentDotPosition)
+                )
+                // Calculate the top-most and the bottom-most coordinates of current dot
+                val leftX = (currentDotPosition * dotSpacing).dp.toPx()
+                val leftYTop = (centerY - dotSize).dp.toPx()
+                val leftYBottom = (centerY + dotSize).dp.toPx()
+                // Calculate the top-most and the bottom-most coordinates of next dot
+                val rightX = (nextDotPosition * dotSpacing).dp.toPx()
+                val rightYTop = (centerY - nextDotSize).dp.toPx()
+                val rightYBottom = (centerY + nextDotSize).dp.toPx()
+                // Calculate the middle Y coordinate between two dots
+                val midX = ((currentDotPosition + 0.5) * dotSpacing).dp.toPx()
+
+                val path = if (shouldFlip) {
+                    // Calculate control point Y coordinates a bit inside the current dot
+                    val bezierYTop = (centerY - dotSize - 5f * nextPositionDelta).dp.toPx()
+                    val bezierYBottom = (centerY + dotSize + 5f * nextPositionDelta).dp.toPx()
+                    getBridgePath(
+                        rightX, rightYTop, rightYBottom, leftX, leftYTop, leftYBottom,
+                        midX, bezierYTop, bezierYBottom, centerY.dp.toPx()
+                    )
+                } else {
+                    // Calculate control point Y coordinates a bit inside the next dot
+                    val bezierYTop = (centerY - nextDotSize - 5f * nextPositionDelta).dp.toPx()
+                    val bezierYBottom = (centerY + nextDotSize + 5f * nextPositionDelta).dp.toPx()
+                    getBridgePath(
+                        leftX, leftYTop, leftYBottom, rightX, rightYTop, rightYBottom,
+                        midX, bezierYTop, bezierYBottom, centerY.dp.toPx()
+                    )
+                }
+                drawPath(path, Color(0xff8eb4e6))
+            }
+            // Draw the current dot
+            drawCircle(
+                getDotColor(position, currentDotPosition),
+                radius = dotSize.dp.toPx(),
+                center = Offset((currentDotPosition * dotSpacing).dp.toPx(), 100.dp.toPx())
+            )
+        }
+    }
+}
+
+/**
+ * Returns a path for a bridge between two dots drawn using two quadratic beziers.
+ *
+ * First bezier is drawn between (startX, startYTop) and (endX, endYTop) coordinates using
+ * (bezierX, bezierYTop) as control point.
+ * Second bezier is drawn between (startX, startYBottom) and (endX, endYBottom) coordinates using
+ * (bezierX, bezierYBottom) as control point.
+ *
+ * Then additional lines are drawn to make this a filled path.
+ */
+private fun getBridgePath(
+    startX: Float,
+    startYTop: Float,
+    startYBottom: Float,
+    endX: Float,
+    endYTop: Float,
+    endYBottom: Float,
+    bezierX: Float,
+    bezierYTop: Float,
+    bezierYBottom: Float,
+    midY: Float
+): Path {
+    return Path().apply {
+        moveTo(startX, startYTop)
+        quadraticBezierTo(bezierX, bezierYTop, endX, endYTop)
+        lineTo(endX, midY)
+        lineTo(startX, midY)
+        moveTo(startX, startYTop)
+        lineTo(startX, startYBottom)
+        quadraticBezierTo(bezierX, bezierYBottom, endX, endYBottom)
+        lineTo(endX, midY)
+        lineTo(startX, midY)
+    }
+}
+
+private fun getDotColor(position: Float, dotIndex: Int): Color {
+    val fraction = min(abs(position - dotIndex), 1f)
+    return lerp(Color(0xff1a73e8), Color(0xff468ce8), fraction)
+}
+
+private fun getDotSizeForPosition(position: Float, dotIndex: Int): Float {
+    val positionDelta = abs(position - dotIndex)
+    return if (positionDelta < 1f) {
+        (10f + 20 * (1 - positionDelta))
+    } else {
+        10f
+    }
+}
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisibilityDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisibilityDemo.kt
index 3e39440..d4f9186 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisibilityDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisibilityDemo.kt
@@ -164,10 +164,9 @@
             targetWidth = { fullWidth -> fullWidth / 10 },
             // Overwrites the default animation with tween for this shrink animation.
             animationSpec = tween(durationMillis = 400)
-        ) + fadeOut()
-    ) {
-        content()
-    }
+        ) + fadeOut(),
+        content = content
+    )
 }
 
 @OptIn(ExperimentalAnimationApi::class)
@@ -188,10 +187,9 @@
             // Overwrites the ending position of the slide-out to 200 (pixels) to the right
             targetOffsetX = { 200 },
             animationSpec = spring(stiffness = Spring.StiffnessHigh)
-        ) + fadeOut()
-    ) {
-        content()
-    }
+        ) + fadeOut(),
+        content = content
+    )
 }
 
 @OptIn(ExperimentalAnimationApi::class)
@@ -206,10 +204,9 @@
         exit = fadeOut(
             // Overwrites the default animation with tween
             animationSpec = tween(durationMillis = 250)
-        )
-    ) {
-        content()
-    }
+        ),
+        content = content
+    )
 }
 
 @OptIn(ExperimentalAnimationApi::class)
@@ -224,8 +221,7 @@
         ) + expandVertically(
             expandFrom = Alignment.Top
         ) + fadeIn(initialAlpha = 0.3f),
-        exit = slideOutVertically() + shrinkVertically() + fadeOut()
-    ) {
-        content()
-    }
+        exit = slideOutVertically() + shrinkVertically() + fadeOut(),
+        content = content
+    )
 }
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
index bfc8ce6..a8aff2e 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimationDemos.kt
@@ -25,11 +25,11 @@
         DemoCategory(
             "State Transition Demos",
             listOf(
-                ComposableDemo("Multi-dimensional prop") { MultiDimensionalAnimationDemo() },
                 ComposableDemo("Double tap to like") { DoubleTapToLikeDemo() },
-                ComposableDemo("Repeating rotation") { RepeatedRotationDemo() },
                 ComposableDemo("Gesture based animation") { GestureBasedAnimationDemo() },
                 ComposableDemo("Infinite transition") { InfiniteTransitionDemo() },
+                ComposableDemo("Multi-dimensional prop") { MultiDimensionalAnimationDemo() },
+                ComposableDemo("Repeating rotation") { RepeatedRotationDemo() },
             )
         ),
         DemoCategory(
@@ -50,15 +50,22 @@
         DemoCategory(
             "Suspend Animation Demos",
             listOf(
-                ComposableDemo("Animated clock") { AnimatedClockDemo() },
                 ComposableDemo("Animated scrolling") { FancyScrollingDemo() },
-                ComposableDemo("animateAsState()") { SingleValueAnimationDemo() },
+                ComposableDemo("animateColorAsState") { SingleValueAnimationDemo() },
                 ComposableDemo("Follow the tap") { SuspendAnimationDemo() },
-                ComposableDemo("Game of fling") { FlingGame() },
                 ComposableDemo("Infinitely Animating") { InfiniteAnimationDemo() },
                 ComposableDemo("Spring back scrolling") { SpringBackScrollingDemo() },
                 ComposableDemo("Swipe to dismiss") { SwipeToDismissDemo() },
             )
         ),
+        DemoCategory(
+            "Fun Demos",
+            listOf(
+                ComposableDemo("Animated clock") { AnimatedClockDemo() },
+                ComposableDemo("Animated dots") { AnimatedDotsDemo() },
+                ComposableDemo("Game of fling") { FlingGame() },
+                ComposableDemo("Spring chain") { SpringChainDemo() },
+            )
+        )
     )
 )
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt
index 643168a..646887f 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt
@@ -17,6 +17,8 @@
 package androidx.compose.animation.demos
 
 import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
 import androidx.compose.animation.ExperimentalAnimationApi
 import androidx.compose.animation.expandHorizontally
 import androidx.compose.animation.expandIn
@@ -26,6 +28,12 @@
 import androidx.compose.animation.shrinkHorizontally
 import androidx.compose.animation.shrinkOut
 import androidx.compose.animation.shrinkVertically
+import androidx.compose.animation.slideIn
+import androidx.compose.animation.slideInHorizontally
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOut
+import androidx.compose.animation.slideOutHorizontally
+import androidx.compose.animation.slideOutVertically
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -34,19 +42,19 @@
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.wrapContentWidth
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.selection.selectable
 import androidx.compose.material.Button
 import androidx.compose.material.Checkbox
-import androidx.compose.material.RadioButton
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateListOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
@@ -66,6 +74,9 @@
 import androidx.compose.ui.Alignment.Companion.TopStart
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 
 @Composable
@@ -75,7 +86,10 @@
 
         var alignment by remember { mutableStateOf(TopStart) }
         var visible by remember { mutableStateOf(true) }
-        val (selectedOption, onOptionSelected) = remember { mutableStateOf(0) }
+        val selectedOptions = remember { mutableStateListOf(false, true, false) }
+        val onOptionSelected: (Int) -> Unit = remember {
+            { selectedOptions[it] = !selectedOptions[it] }
+        }
         Column(Modifier.fillMaxSize()) {
             Button(
                 modifier = Modifier.align(CenterHorizontally),
@@ -116,7 +130,7 @@
                         Text("Bottom\nStart")
                     }
                 }
-                CenterMenu(selectedOption, oppositeAlignment.value, alignment, visible)
+                CenterMenu(selectedOptions, oppositeAlignment.value, alignment, visible)
                 Box(Modifier.fillMaxHeight().wrapContentWidth()) {
                     Button(
                         modifier = Modifier.align(TopStart),
@@ -158,7 +172,7 @@
             }
 
             AlignmentOption(oppositeAlignment)
-            FadeOptions(selectedOption, onOptionSelected)
+            TransitionOptions(selectedOptions, onOptionSelected)
         }
     }
 }
@@ -177,7 +191,7 @@
 @OptIn(ExperimentalAnimationApi::class)
 @Composable
 fun CenterMenu(
-    selectedOption: Int,
+    selectedOptions: List<Boolean>,
     oppositeDirection: Boolean,
     alignment: Alignment,
     visible: Boolean
@@ -185,38 +199,71 @@
     Box(with(RowScope) { Modifier.fillMaxHeight().weight(1f) }) {
 
         val animationAlignment = if (oppositeDirection) opposite(alignment) else alignment
-        val enter = when (animationAlignment) {
+        val expand = when (animationAlignment) {
             TopCenter -> expandVertically(expandFrom = Top)
             BottomCenter -> expandVertically(expandFrom = Bottom)
             CenterStart -> expandHorizontally(expandFrom = Start)
             CenterEnd -> expandHorizontally(expandFrom = End)
             else -> expandIn(animationAlignment)
-        }.run {
-            if (selectedOption >= 1) {
-                this + fadeIn()
-            } else {
-                this
-            }
         }
 
-        val exit = when (animationAlignment) {
+        val shrink = when (animationAlignment) {
             TopCenter -> shrinkVertically(shrinkTowards = Top)
             BottomCenter -> shrinkVertically(shrinkTowards = Bottom)
             CenterStart -> shrinkHorizontally(shrinkTowards = Start)
             CenterEnd -> shrinkHorizontally(shrinkTowards = End)
             else -> shrinkOut(animationAlignment)
-        }.run {
-            if (selectedOption >= 2) {
-                this + fadeOut()
-            } else {
-                this
+        }
+
+        val slideIn = when (alignment) {
+            TopCenter -> slideInVertically({ -it })
+            BottomCenter -> slideInVertically({ it })
+            CenterStart -> slideInHorizontally({ -it })
+            CenterEnd -> slideInHorizontally({ it })
+            TopStart -> slideIn({ IntOffset(-it.width, -it.height) })
+            BottomStart -> slideIn({ IntOffset(-it.width, it.height) })
+            TopEnd -> slideIn({ IntOffset(it.width, -it.height) })
+            BottomEnd -> slideIn({ IntOffset(it.width, it.height) })
+            else -> slideIn({ alignment.align(it, IntSize.Zero, LayoutDirection.Ltr) })
+        }
+        val slideOut = when (alignment) {
+            TopCenter -> slideOutVertically({ -it })
+            BottomCenter -> slideOutVertically({ it })
+            CenterStart -> slideOutHorizontally({ -it })
+            CenterEnd -> slideOutHorizontally({ it })
+            TopStart -> slideOut({ IntOffset(-it.width, -it.height) })
+            BottomStart -> slideOut({ IntOffset(-it.width, it.height) })
+            TopEnd -> slideOut({ IntOffset(it.width, -it.height) })
+            BottomEnd -> slideOut({ IntOffset(it.width, it.height) })
+            else -> slideOut({ alignment.align(IntSize.Zero, it, LayoutDirection.Ltr) })
+        }
+
+        var enter: EnterTransition? = null
+        selectedOptions.forEachIndexed { index: Int, selected: Boolean ->
+            if (selected) {
+                enter = when (index) {
+                    0 -> enter?.plus(fadeIn()) ?: fadeIn()
+                    1 -> enter?.plus(expand) ?: expand
+                    else -> enter?.plus(slideIn) ?: slideIn
+                }
             }
         }
+        var exit: ExitTransition? = null
+        selectedOptions.forEachIndexed { index: Int, selected: Boolean ->
+            if (selected) {
+                exit = when (index) {
+                    0 -> exit?.plus(fadeOut()) ?: fadeOut()
+                    1 -> exit?.plus(shrink) ?: shrink
+                    else -> exit?.plus(slideOut) ?: slideOut
+                }
+            }
+        }
+
         AnimatedVisibility(
             visible,
-            Modifier.align(alignment),
-            enter = enter,
-            exit = exit
+            if (selectedOptions[1]) Modifier.align(alignment) else Modifier,
+            enter = enter ?: fadeIn(),
+            exit = exit ?: fadeOut()
         ) {
             val menuText = remember {
                 mutableListOf<String>().apply {
@@ -235,29 +282,25 @@
 }
 
 @Composable
-fun FadeOptions(selectedOption: Int, onOptionSelected: (Int) -> Unit) {
+fun TransitionOptions(selectedOptions: List<Boolean>, onOptionSelected: (Int) -> Unit) {
     Column {
-        Text(
-            text = "Combine with:",
-            modifier = Modifier.padding(start = 16.dp)
-        )
         val radioOptions =
-            listOf("No Fade", "Fade In", "Fade Out", "Fade In & Fade out")
+            listOf("Fade", "Expand/Shrink", "Slide")
         radioOptions.forEachIndexed { i, text ->
             Row(
                 Modifier
                     .fillMaxWidth()
                     .height(30.dp)
                     .selectable(
-                        selected = (i == selectedOption),
+                        selected = selectedOptions[i],
                         onClick = { onOptionSelected(i) }
                     )
                     .padding(horizontal = 16.dp),
                 verticalAlignment = Alignment.CenterVertically
             ) {
-                RadioButton(
-                    selected = (i == selectedOption),
-                    onClick = { onOptionSelected(i) }
+                Checkbox(
+                    checked = selectedOptions[i],
+                    onCheckedChange = { onOptionSelected(i) }
                 )
                 Text(
                     text = text,
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SpringChainDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SpringChainDemo.kt
new file mode 100644
index 0000000..d03313b
--- /dev/null
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/SpringChainDemo.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.animation.demos
+
+import androidx.compose.animation.core.animateOffsetAsState
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.detectDragGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+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.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.consumeAllChanges
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.round
+
+@Composable
+fun SpringChainDemo() {
+    var leader by remember { mutableStateOf(Offset(200f, 200f)) }
+    Box(
+        Modifier.fillMaxSize().pointerInput(Unit) {
+            detectDragGestures { change, dragAmount ->
+                change.consumeAllChanges()
+                leader += dragAmount
+            }
+        }
+    ) {
+        Text(
+            modifier = Modifier.align(Alignment.Center),
+            text = "Since we are here, why not drag me around?"
+        )
+        val size = pastelAwakening.size
+        val followers = remember { Array<State<Offset>>(size) { mutableStateOf(Offset.Zero) } }
+        for (i in 0 until size) {
+            // Each follower on the spring chain uses the previous follower's position as target
+            followers[i] = animateOffsetAsState(if (i == 0) leader else followers[i - 1].value)
+        }
+
+        // Followers stacked in reverse orders
+        for (i in followers.size - 1 downTo 0) {
+            Box(
+                Modifier
+                    .offset { followers[i].value.round() }
+                    .size(80.dp)
+                    .background(pastelAwakening[i], CircleShape)
+            )
+        }
+        // Leader
+        Box(
+            Modifier.offset { leader.round() }.size(80.dp)
+                .background(Color(0xFFfffbd0), CircleShape)
+        )
+    }
+}
+
+private val pastelAwakening = listOf(
+    Color(0xffdfdeff),
+    Color(0xffffe0f5),
+    Color(0xffffefd8),
+    Color(0xffe6ffd0),
+    Color(0xffd9f6ff)
+)
\ No newline at end of file
diff --git a/compose/animation/animation/lint-baseline.xml b/compose/animation/animation/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/animation/animation/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/animation/animation/samples/lint-baseline.xml b/compose/animation/animation/samples/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/animation/animation/samples/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedVisibilityTest.kt b/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedVisibilityTest.kt
index 27be5f9..2041280 100644
--- a/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedVisibilityTest.kt
+++ b/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedVisibilityTest.kt
@@ -19,6 +19,7 @@
 import androidx.compose.animation.core.FastOutSlowInEasing
 import androidx.compose.animation.core.LinearOutSlowInEasing
 import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
@@ -30,10 +31,14 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
@@ -43,6 +48,7 @@
 import androidx.test.filters.LargeTest
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -62,37 +68,37 @@
     fun animateVisibilityExpandShrinkTest() {
         val testModifier by mutableStateOf(TestModifier())
         var visible by mutableStateOf(false)
-        var density = 0f
         var offset by mutableStateOf(Offset(0f, 0f))
         var disposed by mutableStateOf(false)
         rule.mainClock.autoAdvance = false
         rule.setContent {
-            AnimatedVisibility(
-                visible, testModifier,
-                enter = expandIn(
-                    Alignment.BottomEnd,
-                    { fullSize -> IntSize(fullSize.width / 4, fullSize.height / 2) },
-                    tween(160, easing = LinearOutSlowInEasing)
-                ),
-                exit = shrinkOut(
-                    Alignment.CenterStart,
-                    { fullSize -> IntSize(fullSize.width / 10, fullSize.height / 5) },
-                    tween(160, easing = FastOutSlowInEasing)
-                )
-            ) {
-                Box(
-                    Modifier.onGloballyPositioned {
-                        offset = it.localToRoot(Offset.Zero)
-                    }.requiredSize(100.dp, 100.dp)
+            CompositionLocalProvider(LocalDensity provides Density(1f)) {
+                AnimatedVisibility(
+                    visible, testModifier,
+                    enter = expandIn(
+                        Alignment.BottomEnd,
+                        { fullSize -> IntSize(fullSize.width / 4, fullSize.height / 2) },
+                        tween(160, easing = LinearOutSlowInEasing)
+                    ),
+                    exit = shrinkOut(
+                        Alignment.CenterStart,
+                        { fullSize -> IntSize(fullSize.width / 10, fullSize.height / 5) },
+                        tween(160, easing = FastOutSlowInEasing)
+                    )
                 ) {
-                    DisposableEffect(Unit) {
-                        onDispose {
-                            disposed = true
+                    Box(
+                        Modifier.onGloballyPositioned {
+                            offset = it.localToRoot(Offset.Zero)
+                        }.requiredSize(100.dp, 100.dp)
+                    ) {
+                        DisposableEffect(Unit) {
+                            onDispose {
+                                disposed = true
+                            }
                         }
                     }
                 }
             }
-            density = LocalDensity.current.density
         }
 
         rule.runOnIdle {
@@ -101,9 +107,9 @@
         rule.mainClock.advanceTimeByFrame()
         rule.mainClock.advanceTimeByFrame()
 
-        val startWidth = density * 100 / 4f
-        val startHeight = density * 100 / 2f
-        val fullSize = density * 100
+        val startWidth = 100 / 4f
+        val startHeight = 100 / 2f
+        val fullSize = 100f
         assertFalse(disposed)
 
         for (i in 0..160 step frameDuration) {
@@ -126,8 +132,8 @@
         rule.mainClock.advanceTimeByFrame()
         rule.mainClock.advanceTimeByFrame()
 
-        val endWidth = density * 100 / 10f
-        val endHeight = density * 100 / 5f
+        val endWidth = 100 / 10f
+        val endHeight = 100 / 5f
         for (i in 0..160 step frameDuration) {
             val fraction = FastOutSlowInEasing.transform(i / 160f)
             val animWidth = lerp(fullSize, endWidth, fraction)
@@ -317,4 +323,60 @@
             assertEquals(30, testModifier.width)
         }
     }
+
+    @OptIn(ExperimentalAnimationApi::class)
+    @Test
+    fun animateVisibilityFadeTest() {
+        var visible by mutableStateOf(false)
+        val colors = mutableListOf<Int>()
+        rule.setContent {
+            Box(Modifier.size(size = 20.dp).background(Color.Black)) {
+                AnimatedVisibility(
+                    visible,
+                    enter = fadeIn(animationSpec = tween(500)),
+                    exit = fadeOut(animationSpec = tween(500)),
+                    modifier = Modifier.testTag("AnimV")
+                ) {
+                    Box(modifier = Modifier.size(size = 20.dp).background(Color.White))
+                }
+            }
+        }
+        rule.runOnIdle {
+            visible = true
+        }
+        rule.mainClock.autoAdvance = false
+        while (colors.isEmpty() || colors.last() != 0xffffffff.toInt()) {
+            rule.mainClock.advanceTimeByFrame()
+            rule.onNodeWithTag("AnimV").apply {
+                val data = IntArray(1)
+                data[0] = 0
+                captureToImage().readPixels(data, 10, 10, 1, 1)
+                colors.add(data[0])
+            }
+        }
+        for (i in 1 until colors.size) {
+            // Check every color against the previous one to ensure the alpha is non-decreasing
+            // during fade in.
+            assertTrue(colors[i] >= colors[i - 1])
+        }
+        assertTrue(colors[0] < 0xfffffffff)
+        colors.clear()
+        rule.runOnIdle {
+            visible = false
+        }
+        while (colors.isEmpty() || colors.last() != 0xff000000.toInt()) {
+            rule.mainClock.advanceTimeByFrame()
+            rule.onNodeWithTag("AnimV").apply {
+                val data = IntArray(1)
+                data[0] = 0
+                captureToImage().readPixels(data, 10, 10, 1, 1)
+                colors.add(data[0])
+            }
+        }
+        for (i in 1 until colors.size) {
+            // Check every color against the previous one to ensure the alpha is non-increasing
+            // during fade out.
+            assertTrue(colors[i] <= colors[i - 1])
+        }
+    }
 }
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/ColorVectorConverter.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/ColorVectorConverter.kt
index 738b6b4..2b1411e 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/ColorVectorConverter.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/ColorVectorConverter.kt
@@ -21,6 +21,7 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.colorspace.ColorSpace
 import androidx.compose.ui.graphics.colorspace.ColorSpaces
+import kotlin.math.pow
 
 /**
  * A lambda that takes a [ColorSpace] and returns a converter that can both convert a [Color] to
@@ -30,21 +31,35 @@
 private val ColorToVector: (colorSpace: ColorSpace) -> TwoWayConverter<Color, AnimationVector4D> =
     { colorSpace ->
         TwoWayConverter(
-            convertToVector = {
-                val linearColor = it.convert(ColorSpaces.LinearExtendedSrgb)
-                AnimationVector4D(
-                    linearColor.alpha, linearColor.red, linearColor.green,
-                    linearColor.blue
-                )
+            convertToVector = { color ->
+                // TODO: use Oklab when it is public API
+                val colorXyz = color.convert(ColorSpaces.CieXyz)
+                val x = colorXyz.red
+                val y = colorXyz.green
+                val z = colorXyz.blue
+
+                val l = multiplyColumn(0, x, y, z, M1).pow(1f / 3f)
+                val a = multiplyColumn(1, x, y, z, M1).pow(1f / 3f)
+                val b = multiplyColumn(2, x, y, z, M1).pow(1f / 3f)
+                AnimationVector4D(color.alpha, l, a, b)
             },
             convertFromVector = {
-                Color(
-                    alpha = it.v1.coerceIn(0.0f, 1.0f),
-                    red = it.v2.coerceIn(0.0f, 1.0f),
-                    green = it.v3.coerceIn(0.0f, 1.0f),
-                    blue = it.v4.coerceIn(0.0f, 1.0f),
-                    colorSpace = ColorSpaces.LinearExtendedSrgb
-                ).convert(colorSpace)
+                val l = it.v2.pow(3f)
+                val a = it.v3.pow(3f)
+                val b = it.v4.pow(3f)
+
+                val x = multiplyColumn(0, l, a, b, InverseM1)
+                val y = multiplyColumn(1, l, a, b, InverseM1)
+                val z = multiplyColumn(2, l, a, b, InverseM1)
+
+                val colorXyz = Color(
+                    alpha = it.v1.coerceIn(0f, 1f),
+                    red = x.coerceIn(-2f, 2f),
+                    green = y.coerceIn(-2f, 2f),
+                    blue = z.coerceIn(-2f, 2f),
+                    colorSpace = ColorSpaces.CieXyz // here we have the right color space
+                )
+                colorXyz.convert(colorSpace)
             }
         )
     }
@@ -56,4 +71,22 @@
  */
 val Color.Companion.VectorConverter:
     (colorSpace: ColorSpace) -> TwoWayConverter<Color, AnimationVector4D>
-        get() = ColorToVector
\ No newline at end of file
+        get() = ColorToVector
+
+// These are utilities and constants to emulate converting to/from Oklab color space.
+// These can be removed when Oklab becomes public and we can use it directly in the conversion.
+private val M1 = floatArrayOf(
+    0.80405736f, 0.026893456f, 0.04586542f,
+    0.3188387f, 0.9319606f, 0.26299807f,
+    -0.11419419f, 0.05105356f, 0.83999807f
+)
+
+private val InverseM1 = floatArrayOf(
+    1.2485008f, -0.032856926f, -0.057883114f,
+    -0.48331892f, 1.1044513f, -0.3194066f,
+    0.19910365f, -0.07159331f, 1.202023f
+)
+
+private fun multiplyColumn(column: Int, x: Float, y: Float, z: Float, matrix: FloatArray): Float {
+    return x * matrix[column] + y * matrix[3 + column] + z * matrix[6 + column]
+}
\ No newline at end of file
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/Crossfade.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/Crossfade.kt
index 6a8aec4..3c9786f 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/Crossfade.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/Crossfade.kt
@@ -29,7 +29,7 @@
 import androidx.compose.runtime.mutableStateListOf
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.util.fastForEach
 
 /**
@@ -70,7 +70,7 @@
                 val alpha by transition.animateFloat(
                     transitionSpec = { animationSpec }
                 ) { if (it == key) 1f else 0f }
-                Box(Modifier.alpha(alpha = alpha)) {
+                Box(Modifier.graphicsLayer { this.alpha = alpha }) {
                     content(key)
                 }
             }
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
index 0d2890b7..e1f756b 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
@@ -93,6 +93,12 @@
         )
     }
     // TODO: Support EnterTransition.None
+
+    override fun equals(other: Any?): Boolean {
+        return other is EnterTransition && other.data == data
+    }
+
+    override fun hashCode(): Int = data.hashCode()
 }
 
 /**
@@ -143,7 +149,13 @@
             )
         )
     }
+
     // TODO: Support ExitTransition.None
+    override fun equals(other: Any?): Boolean {
+        return other is ExitTransition && other.data == data
+    }
+
+    override fun hashCode(): Int = data.hashCode()
 }
 
 /**
@@ -825,12 +837,8 @@
     override val isRunning: Boolean
         get() = alphaAnim.isRunning
     override val modifier: Modifier
-        get() = if (alphaAnim.isRunning || (state == AnimStates.Exiting && exit != null)) {
-            // Only add graphics layer if the animation is running, or if it's waiting for other
-            // exit animations to finish.
-            Modifier.graphicsLayer(alpha = alphaAnim.value)
-        } else {
-            Modifier
+        get() = Modifier.graphicsLayer {
+            alpha = alphaAnim.value
         }
 
     override var state: AnimStates = AnimStates.Gone
@@ -851,8 +859,8 @@
                     enter?.apply {
                         // If fade in is defined start from pre-defined `alphaFrom`. If no fade in is defined,
                         // snap the alpha to 1f
+                        alphaAnim = Animatable(alpha, 0.02f)
                         scope.launch {
-                            alphaAnim.snapTo(alpha)
                             alphaAnim.animateTo(1f, animationSpec)
                             listener(AnimationEndReason.Finished, alphaAnim.value)
                         }
@@ -877,6 +885,7 @@
             }
             field = value
         }
+
     private fun animateTo(
         target: Float,
         animationSpec: FiniteAnimationSpec<Float> = spring(visibilityThreshold = 0.02f),
@@ -888,7 +897,7 @@
         }
     }
 
-    val alphaAnim = Animatable(1f, visibilityThreshold = 0.02f)
+    var alphaAnim = Animatable(1f, visibilityThreshold = 0.02f)
 }
 
 private class SlideTransition(
@@ -940,12 +949,15 @@
             // Animation is interrupted from slide out, now slide in
             enter?.apply {
                 // If slide in animation specified, use that. Otherwise use default.
-                val anim = slideAnim
-                    ?: Animatable(
+                val anim = if (slideAnim?.isRunning != true) {
+                    Animatable(
                         slideOffset(fullSize), IntOffset.VectorConverter, IntOffset(1, 1)
                     )
+                } else {
+                    slideAnim
+                }
                 scope.launch {
-                    anim.animateTo(IntOffset.Zero, animationSpec)
+                    anim!!.animateTo(IntOffset.Zero, animationSpec)
                     listener(AnimationEndReason.Finished, anim.value)
                 }
                 slideAnim = anim
@@ -1028,10 +1040,11 @@
                 val anim = sizeAnim?.run {
                     // If the animation is not running and the alignment isn't the same, prefer
                     // AlignmentBasedSizeAnimation over rect based animation.
-                    if (!isRunning && alignment != enter.alignment) {
+                    if (!isAnimating) {
                         null
-                    } else
+                    } else {
                         this
+                    }
                 } ?: AlignmentBasedSizeAnimation(
                     Animatable(
                         enter.startSize.invoke(fullSize),
@@ -1128,11 +1141,6 @@
     init {
         animations = mutableListOf()
         // Only set up animations when either enter or exit transition is defined.
-        if (enter.data.fade != null || exit.data.fade != null) {
-            animations.add(
-                FadeTransition(enter.data.fade, exit.data.fade, scope, listener)
-            )
-        }
         if (enter.data.slide != null || exit.data.slide != null) {
             animations.add(
                 SlideTransition(enter.data.slide, exit.data.slide, scope, listener)
@@ -1143,6 +1151,11 @@
                 ChangeSizeTransition(enter.data.changeSize, exit.data.changeSize, scope, listener)
             )
         }
+        if (enter.data.fade != null || exit.data.fade != null) {
+            animations.add(
+                FadeTransition(enter.data.fade, exit.data.fade, scope, listener)
+            )
+        }
     }
 
     val modifier: Modifier
diff --git a/compose/animation/animation/src/test/kotlin/androidx/compose/animation/ConverterTest.kt b/compose/animation/animation/src/test/kotlin/androidx/compose/animation/ConverterTest.kt
index 49a60a3..1134f6c 100644
--- a/compose/animation/animation/src/test/kotlin/androidx/compose/animation/ConverterTest.kt
+++ b/compose/animation/animation/src/test/kotlin/androidx/compose/animation/ConverterTest.kt
@@ -37,12 +37,12 @@
     @Test
     fun testColorConverter() {
         val converter = (Color.VectorConverter)(ColorSpaces.Srgb)
-        assertEquals(converter.convertFromVector(AnimationVector4D(1f, 1f, 0f, 0f)), Color.Red)
-        assertEquals(converter.convertToVector(Color.Green), AnimationVector4D(1f, 0f, 1f, 0f))
-        assertEquals(
-            converter.convertFromVector(AnimationVector4D(0f, 0f, 0f, 1f)),
-            Color(alpha = 0f, red = 0f, green = 0f, blue = 1f)
-        )
+        val vectorFromRed = converter.convertToVector(Color.Red)
+        assertEquals(Color.Red, converter.convertFromVector(vectorFromRed))
+        val vectorFromGreen = converter.convertToVector(Color.Green)
+        assertEquals(Color.Green, converter.convertFromVector(vectorFromGreen))
+        val vectorFromBlue = converter.convertToVector(Color.Blue)
+        assertEquals(Color.Blue, converter.convertFromVector(vectorFromBlue))
     }
 
     @Test
@@ -51,25 +51,53 @@
 
         // Alpha channel above 1.0f clamps to 1.0f and result is red
         assertEquals(
-            converter.convertFromVector(AnimationVector4D(1.1f, 1f, 0f, 0f)),
-            Color.Red
+            1f,
+            converter.convertFromVector(AnimationVector4D(1.1f, 1f, 0f, 0f)).alpha,
+            0f
         )
         // Alpha channel below 0.0f clamps to 0.0f and the result is transparent red
         assertEquals(
-            converter.convertFromVector(AnimationVector4D(-0.1f, 1f, 0f, 0f)),
-            Color.Red.copy(alpha = 0.0f)
+            0f,
+            converter.convertFromVector(AnimationVector4D(-0.1f, 1f, 0f, 0f))
+                .alpha,
+            0f
         )
 
-        // Red channel above 1.0f clamps to 1.0f and the result is red
+        // all channels should clamp:
         assertEquals(
-            converter.convertFromVector(AnimationVector4D(1.0f, 1.1f, 0f, 0f)),
-            Color.Red
+            1f,
+            converter.convertFromVector(AnimationVector4D(1.0f, 3f, 3f, 3f)).red,
+            0f
+        )
+        assertEquals(
+            1f,
+            converter.convertFromVector(AnimationVector4D(1.0f, 3f, 3f, 3f)).green,
+            0f
+        )
+        assertEquals(
+            1f,
+            converter.convertFromVector(AnimationVector4D(1.0f, 3f, 3f, 3f)).blue,
+            0f
         )
 
-        // Red channel below 0.0f clamps to 0.0f and the result is black
+        // All channel below 0.0f clamps to 0.0f and the result is black
         assertEquals(
-            converter.convertFromVector(AnimationVector4D(1.0f, -0.1f, 0f, 0f)),
-            Color.Black
+            0f,
+            converter.convertFromVector(AnimationVector4D(1.0f, -3f, -3f, -3f))
+                .red,
+            0f
+        )
+        assertEquals(
+            0f,
+            converter.convertFromVector(AnimationVector4D(1.0f, -3f, -3f, -3f))
+                .green,
+            0f
+        )
+        assertEquals(
+            0f,
+            converter.convertFromVector(AnimationVector4D(1.0f, -3f, -3f, -3f))
+                .blue,
+            0f
         )
 
         // Green channel above 1.0f clamps to 1.0f and the result is green
@@ -77,24 +105,6 @@
             converter.convertFromVector(AnimationVector4D(1.0f, 0.0f, 1.1f, 0f)),
             Color.Green
         )
-
-        // Green channel below 0.0f clamps to 0.0f and result is black
-        assertEquals(
-            converter.convertFromVector(AnimationVector4D(1.0f, 0f, -0.1f, 0f)),
-            Color.Black
-        )
-
-        // Blue channel above 1.0f clamps to 1.0f and result is blue
-        assertEquals(
-            converter.convertFromVector(AnimationVector4D(1.0f, 0f, 0f, 1.1f)),
-            Color.Blue
-        )
-
-        // Blue channel below 0.0f clamps to 0.0f and the result is black
-        assertEquals(
-            converter.convertFromVector(AnimationVector4D(1.0f, 0f, 0f, -0.1f)),
-            Color.Black
-        )
     }
 
     @Test
diff --git a/compose/benchmark-utils/benchmark/build.gradle b/compose/benchmark-utils/benchmark/build.gradle
index 699caad..ab872da 100644
--- a/compose/benchmark-utils/benchmark/build.gradle
+++ b/compose/benchmark-utils/benchmark/build.gradle
@@ -15,7 +15,6 @@
  */
 
 import static androidx.build.dependencies.DependenciesKt.*
-import androidx.build.Publish
 
 plugins {
     id("AndroidXPlugin")
diff --git a/compose/compiler/compiler-hosted/integration-tests/kotlin-compiler-repackaged/lint-baseline.xml b/compose/compiler/compiler-hosted/integration-tests/kotlin-compiler-repackaged/lint-baseline.xml
deleted file mode 100644
index 297ae16..0000000
--- a/compose/compiler/compiler-hosted/integration-tests/kotlin-compiler-repackaged/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" version="4.2.0-beta02">
-
-</issues>
diff --git a/compose/compiler/compiler-hosted/integration-tests/lint-baseline.xml b/compose/compiler/compiler-hosted/integration-tests/lint-baseline.xml
deleted file mode 100644
index 8f1aa4b..0000000
--- a/compose/compiler/compiler-hosted/integration-tests/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-</issues>
diff --git a/compose/compiler/compiler-hosted/lint-baseline.xml b/compose/compiler/compiler-hosted/lint-baseline.xml
deleted file mode 100644
index 297ae16..0000000
--- a/compose/compiler/compiler-hosted/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" version="4.2.0-beta02">
-
-</issues>
diff --git a/compose/compiler/compiler/lint-baseline.xml b/compose/compiler/compiler/lint-baseline.xml
deleted file mode 100644
index 297ae16..0000000
--- a/compose/compiler/compiler/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" version="4.2.0-beta02">
-
-</issues>
diff --git a/compose/compiler/settings.gradle b/compose/compiler/settings.gradle
index 316999f..b5b7bedf 100644
--- a/compose/compiler/settings.gradle
+++ b/compose/compiler/settings.gradle
@@ -21,7 +21,8 @@
 selectProjectsFromAndroidX({ name ->
     if (name.startsWith(":compose:compiler")) return true
     if (name == ":compose:androidview:androidview") return true
-    if (name == ":compose:internal-lint-checks") return true
+    if (name == ":compose:lint:common") return true
+    if (name == ":compose:lint:internal-lint-checks") return true
     return false
 })
 
diff --git a/compose/desktop/desktop/build.gradle b/compose/desktop/desktop/build.gradle
index 14a1c9a..ce53db3 100644
--- a/compose/desktop/desktop/build.gradle
+++ b/compose/desktop/desktop/build.gradle
@@ -15,8 +15,8 @@
  */
 
 import androidx.build.LibraryGroups
+import androidx.build.LibraryType
 import androidx.build.LibraryVersions
-import androidx.build.Publish
 import androidx.build.RunApiTasks
 import androidx.build.SupportConfigKt
 
@@ -74,7 +74,7 @@
 
 androidx {
     name = "Jetpack Compose desktop implementation"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.DESKTOP
     inceptionYear = "2020"
     legacyDisableKotlinStrictApiMode = true
diff --git a/compose/docs/compose-api-guidelines.md b/compose/docs/compose-api-guidelines.md
new file mode 100644
index 0000000..3219f86
--- /dev/null
+++ b/compose/docs/compose-api-guidelines.md
@@ -0,0 +1,837 @@
+# API Guidelines for Jetpack Compose
+
+## Last updated: March 10, 2021
+
+# Who this document is for
+
+The Compose API guidelines outline the patterns, best practices and prescriptive style guidelines for writing idiomatic Jetpack Compose APIs. As Jetpack Compose code is built in layers, everyone writing code that uses Jetpack Compose is building their own API to consume themselves.
+
+This document assumes a familiarity with Jetpack Compose's runtime APIs including `@Composable`, `remember {}` and `CompositionLocal`.
+
+The requirement level of each of these guidelines is specified using the terms set forth in [RFC2119](https://www.ietf.org/rfc/rfc2119.txt) for each of the following developer audiences. If an audience is not specifically named with a requirement level for a guideline, it should be assumed that the guideline is OPTIONAL for that audience.
+
+## Jetpack Compose framework development
+
+Contributions to the `androidx.compose` libraries and tools generally follow these guidelines to a strict degree in order to promote consistency, setting expectations and examples for consumer code at all layers.
+
+## Library development based on Jetpack Compose
+
+It is expected and desired that an ecosystem of external libraries will come to exist that target Jetpack Compose, exposing a public API of `@Composable` functions and supporting types for consumption by apps and other libraries. While it is desirable for these libraries to follow these guidelines to the same degree as Jetpack Compose framework development would, organizational priorities and local consistency may make it appropriate for some purely stylistic guidelines to be relaxed.
+
+## App development based on Jetpack Compose
+
+App development is often subject to strong organizational priorities and norms as well as requirements to integrate with existing app architecture. This may call for not only stylistic deviation from these guidelines but structural deviation as well. Where possible, alternative approaches for app development will be listed in this document that may be more appropriate in these situations.
+
+# Kotlin style
+
+## Baseline style guidelines
+
+**Jetpack Compose framework development** MUST follow the Kotlin Coding Conventions outlined at https://kotlinlang.org/docs/reference/coding-conventions.html as a baseline with the additional adjustments described below.
+
+**Jetpack Compose Library and app development** SHOULD also follow this same guideline.
+
+### Why
+
+The Kotlin Coding Conventions establish a standard of consistency for the Kotlin ecosystem at large. The additional style guidelines that follow in this document for Jetpack Compose account for Jetpack Compose's language-level extensions, mental models, and intended data flows, establishing consistent conventions and expectations around Compose-specific patterns.
+
+## Singletons, constants, sealed class and enum class values
+
+**Jetpack Compose framework development** MUST name deeply immutable constants following the permitted object declaration convention of `PascalCase` as documented [here](https://kotlinlang.org/docs/reference/coding-conventions.html#property-names) as a replacement for any usage of `CAPITALS_AND_UNDERSCORES`. Enum class values MUST also be named using `PascalCase` as documented in the same section.
+
+**Library development** SHOULD follow this same convention when targeting or extending Jetpack Compose.
+
+**App Development** MAY follow this convention.
+
+### Why
+
+Jetpack Compose discourages the use and creation of singletons or companion object state that cannot be treated as _stable_ over time and across threads, reducing the usefulness of a distinction between singleton objects and other forms of constants. This forms a consistent expectation of API shape for consuming code whether the implementation detail is a top-level `val`, a `companion object`, an `enum class`, or a `sealed class` with nested `object` subclasses. `myFunction(Foo)` and `myFunction(Foo.Bar)` carry the same meaning and intent for calling code regardless of specific implementation details.
+
+Library and app code with a strong existing investment in `CAPITALS_AND_UNDERSCORES` in their codebase MAY opt for local consistency with that pattern instead.
+
+### Do
+
+```kotlin
+const val DefaultKeyName = "__defaultKey"
+
+val StructurallyEqual: ComparisonPolicy = StructurallyEqualsImpl(...)
+
+object ReferenceEqual : ComparisonPolicy {
+    // ...
+}
+
+sealed class LoadResult<T> {
+    object Loading : LoadResult<Nothing>()
+    class Done(val result: T) : LoadResult<T>()
+    class Error(val cause: Throwable) : LoadResult<Nothing>()
+}
+
+enum class Status {
+    Idle,
+    Busy
+}
+```
+
+### Don't
+
+```kotlin
+const val DEFAULT_KEY_NAME = "__defaultKey"
+
+val STRUCTURALLY_EQUAL: ComparisonPolicy = StructurallyEqualsImpl(...)
+
+object ReferenceEqual : ComparisonPolicy {
+    // ...
+}
+
+sealed class LoadResult<T> {
+    object Loading : LoadResult<Nothing>()
+    class Done(val result: T) : LoadResult<T>()
+    class Error(val cause: Throwable) : LoadResult<Nothing>()
+}
+
+enum class Status {
+    IDLE,
+    BUSY
+}
+```
+
+# Compose baseline
+
+The Compose compiler plugin and runtime establish new language facilities for Kotlin and the means to interact with them. This layer adds a declarative programming model for constructing and managing mutable tree data structures over time. Compose UI is an example of one type of tree that the Compose runtime can manage, but it is not limited to that use.
+
+This section outlines guidelines for `@Composable` functions and APIs that build on the Compose runtime capabilities. These guidelines apply to all Compose runtime-based APIs, regardless of the managed tree type.
+
+## Naming Unit @Composable functions as entities
+
+**Jetpack Compose framework development and Library development** MUST name any function that returns `Unit` and bears the `@Composable` annotation using `PascalCase`, and the name MUST be that of a noun, not a verb or verb phrase, nor a nouned preposition, adjective or adverb. Nouns MAY be prefixed by descriptive adjectives. This guideline applies whether the function emits UI elements or not.
+
+**App development** SHOULD follow this same convention.
+
+### Why
+
+Composable functions that return `Unit` are considered _declarative entities_ that can be either _present_ or _absent_ in a composition and therefore follow the naming rules for classes. A composable's presence or absence resulting from the evaluation of its caller's control flow establishes both persistent identity across recompositions and a lifecycle for that persistent identity. This naming convention promotes and reinforces this declarative mental model.
+
+### Do
+
+```kotlin
+// This function is a descriptive PascalCased noun as a visual UI element
+@Composable
+fun FancyButton(text: String, onClick: () -> Unit) {
+```
+
+### Do
+
+```kotlin
+// This function is a descriptive PascalCased noun as a non-visual element
+// with presence in the composition
+@Composable
+fun BackButtonHandler(onBackPressed: () -> Unit) {
+```
+
+### Don't
+
+```kotlin
+// This function is a noun but is not PascalCased!
+@Composable
+fun fancyButton(text: String, onClick: () -> Unit) {
+```
+
+### Don't
+
+```kotlin
+// This function is PascalCased but is not a noun!
+@Composable
+fun RenderFancyButton(text: String, onClick: () -> Unit) {
+```
+
+### Don't
+
+```kotlin
+// This function is neither PascalCased nor a noun!
+@Composable
+fun drawProfileImage(image: ImageAsset) {
+```
+
+## Naming @Composable functions that return values
+
+**Jetpack Compose framework development and Library development** MUST follow the standard [Kotlin Coding Conventions for the naming of functions](https://kotlinlang.org/docs/reference/coding-conventions.html#function-names) for any function annotated `@Composable` that returns a value other than `Unit`.
+
+**Jetpack Compose framework development and Library development** MUST NOT use the factory function exemption in the [Kotlin Coding Conventions for the naming of functions](https://kotlinlang.org/docs/reference/coding-conventions.html#function-names) for naming any function annotated `@Composable` as a PascalCase type name matching the function's abstract return type.
+
+### Why
+
+While useful and accepted outside of `@Composable` functions, this factory function convention has drawbacks that set inappropriate expectations for callers when used with `@Composable` functions.
+
+Primary motivations for marking a factory function as `@Composable` include using composition to establish a managed lifecycle for the object or using `CompositionLocal`s as inputs to the object's construction. The former implies the use of Compose's `remember {}` API to cache and maintain the object instance across recompositions, which can break caller expectations around a factory operation that reads like a constructor call. (See the next section.) The latter motivation implies unseen inputs that should be expressed in the factory function name.
+
+Additionally, the mental model of `Unit`-returning `@Composable` functions as declarative entities should not be confused with a, "virtual DOM" mental model. Returning values from `@Composable` functions named as `PascalCase` nouns promotes this confusion, and may promote an undesirable style of returning a stateful control surface for a present UI entity that would be better expressed and more useful as a hoisted state object.
+
+More information about state hoisting patterns can be found in the design patterns section of this document.
+
+### Do
+
+```kotlin
+// Returns a style based on the current CompositionLocal settings
+// This function qualifies where its value comes from
+@Composable
+fun defaultStyle(): Style {
+```
+
+### Don't
+
+```kotlin
+// Returns a style based on the current CompositionLocal settings
+// This function looks like it's constructing a context-free object!
+@Composable
+fun Style(): Style {
+```
+
+## Naming @Composable functions that remember {} the objects they return
+
+**Jetpack Compose framework development and Library development** MUST prefix any `@Composable` factory function that internally `remember {}`s and returns a mutable object with the prefix `remember`.
+
+**App development** SHOULD follow this same convention.
+
+### Why
+
+An object that can change over time and persists across recompositions carries observable side effects that should be clearly communicated to a caller. This also signals that a caller does not need to duplicate a `remember {}` of the object at the call site to attain this persistence.
+
+### Do
+
+```kotlin
+// Returns a CoroutineScope that will be cancelled when this call
+// leaves the composition
+// This function is prefixed with remember to describe its behavior
+@Composable
+fun rememberCoroutineScope(): CoroutineScope {
+```
+
+### Don't
+
+```kotlin
+// Returns a CoroutineScope that will be cancelled when this call leaves
+// the composition
+// This function's name does not suggest automatic cancellation behavior!
+@Composable
+fun createCoroutineScope(): CoroutineScope {
+```
+
+Note that returning an object is not sufficient to consider a function to be a factory function; it must be the function's primary purpose. Consider a `@Composable` function such as `Flow<T>.collectAsState()`; this function's primary purpose is to establish a subscription to a `Flow`; that it `remember {}`s its returned `State<T>` object is incidental.
+
+## Naming CompositionLocals
+
+A `CompositionLocal` is a key into a composition-scoped key-value table. `CompositionLocal`s may be used to provide global-like values to a specific subtree of composition.
+
+**Jetpack Compose framework development and Library development** MUST NOT name `CompositionLocal` keys using "CompositionLocal" or "Local" as a noun suffix. `CompositionLocal` keys should bear a descriptive name based on their value.
+
+**Jetpack Compose framework development and Library development** MAY use "Local" as a prefix for a `CompositionLocal` key name if no other, more descriptive name is suitable.
+
+### Do
+
+```kotlin
+// "Local" is used here as an adjective, "Theme" is the noun.
+val LocalTheme = staticCompositionLocalOf<Theme>()
+```
+
+### Don't
+
+```kotlin
+// "Local" is used here as a noun!
+val ThemeLocal = staticCompositionLocalOf<Theme>()
+```
+
+## Stable types
+
+The Compose runtime exposes two annotations that may be used to mark a type or function as _stable_ - safe for optimization by the Compose compiler plugin such that the Compose runtime may skip calls to functions that accept only safe types because their results cannot change unless their inputs change.
+
+The Compose compiler plugin may infer these properties of a type automatically, but interfaces and other types for which stability can not be inferred, only promised, may be explicitly annotated. Collectively these types are called, "stable types."
+
+**`@Immutable`** indicates a type where the value of any properties will **never** change after the object is constructed, and all methods are **referentially transparent**. All Kotlin types that may be used in a `const` expression (primitive types and Strings) are considered `@Immutable`.
+
+**`@Stable`** when applied to a type indicates a type that is **mutable**, but the Compose runtime will be notified if and when any public properties or method behavior would yield different results from a previous invocation. (In practice this notification is backed by the `Snapshot` system via `@Stable` `MutableState` objects returned by `mutableStateOf()`.) Such a type may only back its properties using other `@Stable` or `@Immutable` types.
+
+**Jetpack Compose framework development, Library development and App development** MUST ensure in custom implementations of `.equals()` for `@Stable` types that for any two references `a` and `b` of `@Stable` type `T`, `a.equals(b)` MUST **always** return the same value. This implies that any **future** changes to `a` must also be reflected in `b` and vice versa.
+
+This constraint is always met implicitly if `a === b`; the default reference equality implementation of `.equals()` for objects is always a correct implementation of this contract.
+
+**Jetpack Compose framework development and Library development** SHOULD correctly annotate `@Stable` and `@Immutable` types that they expose as part of their public API.
+
+**Jetpack  Compose framework development and Library development** MUST NOT remove the `@Stable` or `@Immutable` annotation from a type if it was declared with one of these annotations in a previous stable release.
+
+**Jetpack Compose framework development and Library development** MUST NOT add the `@Stable` or `@Immutable` annotation to an existing non-final type that was available in a previous stable release without this annotation.
+
+### Why?
+
+`@Stable` and `@Immutable` are behavioral contracts that impact the binary compatibility of code generated by the Compose compiler plugin. Libraries should not declare more restrictive contracts for preexisting non-final types that existing implementations in the wild may not correctly implement, and similarly they may not declare that a library type no longer obeys a previously declared contract that existing code may depend upon.
+
+Implementing the stable contract incorrectly for a type annotated as `@Stable` or `@Immutable` will result in incorrect behavior for `@Composable` functions that accept that type as a parameter or receiver.
+
+## Emit XOR return a value
+
+`@Composable` functions should either emit content into the composition or return a value, but not both. If a composable should offer additional control surfaces to its caller, those control surfaces or callbacks should be provided as parameters to the composable function by the caller.
+
+**Jetpack Compose framework development and Library development** MUST NOT expose any single `@Composable` function that both emits tree nodes and returns a value.
+
+### Why
+
+Emit operations must occur in the order the content is to appear in the composition. Using return values to communicate with the caller restricts the shape of calling code and prevents interactions with other declarative calls that come before it.
+
+### Do
+
+```kotlin
+// Emits a text input field element that will call into the inputState
+// interface object to request changes
+@Composable
+fun InputField(inputState: InputState) {
+// ...
+
+// Communicating with the input field is not order-dependent
+val inputState = remember { InputState() }
+
+Button("Clear input", onClick = { inputState.clear() })
+
+InputField(inputState)
+```
+
+### Don't
+
+```kotlin
+// Emits a text input field element and returns an input value holder
+@Composable
+fun InputField(): UserInputState {
+// ...
+
+// Communicating with the InputField is made difficult
+Button("Clear input", onClick = { TODO("???") })
+val inputState = InputField()
+```
+
+Communicating with a composable by passing parameters forward affords aggregation of several such parameters into types used as parameters to their callers:
+
+```kotlin
+interface DetailCardState {
+    val actionRailState: ActionRailState
+    // ...
+}
+
+@Composable
+fun DetailCard(state: DetailCardState) {
+    Surface {
+        // ...
+        ActionRail(state.actionRailState)
+    }
+}
+
+@Composable
+fun ActionRail(state: ActionRailState) {
+    // ...
+}
+```
+
+For more information on this pattern, see the sections on [hoisted state types](#hoisted-state-types) in the Compose API design patterns section below.
+
+# Compose UI API structure
+
+Compose UI is a UI toolkit built on the Compose runtime. This section outlines guidelines for APIs that use and extend the Compose UI toolkit.
+
+## Compose UI elements
+
+A `@Composable` function that emits exactly one Compose UI tree node is called an _element_.
+
+Example:
+
+```kotlin
+@Composable
+fun SimpleLabel(
+    text: String,
+    modifier: Modifier = Modifier
+) {
+```
+
+**Jetpack Compose framework development and Library development** MUST follow all guidelines in this section.
+
+**Jetpack Compose app development** SHOULD follow all guidelines in this section.
+
+### Elements return Unit
+
+Elements MUST emit their root UI node either directly by calling emit() or by calling another Compose UI element function. They MUST NOT return a value. All behavior of the element not available from the state of the composition MUST be provided by parameters passed to the element function.
+
+#### Why?
+
+Elements are declarative entities in a Compose UI composition. Their presence or absence in the composition determines whether they appear in the resulting UI. Returning a value is not necessary; any means of controlling the emitted element should be provided as a parameter to the element function, not returned by calling the element function. See the, "hoisted state" section in the Compose API design patterns section of this document for more information.
+
+#### Do
+
+```kotlin
+@Composable
+fun FancyButton(
+    text: String,
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier
+) {
+```
+
+#### Don't
+
+```kotlin
+interface ButtonState {
+    val clicks: Flow<ClickEvent>
+    val measuredSize: Size
+}
+
+@Composable
+fun FancyButton(
+    text: String,
+    modifier: Modifier = Modifier
+): ButtonState {
+```
+
+### Elements accept and respect a Modifier parameter
+
+Element functions MUST accept a parameter of type `Modifier`. This parameter MUST be named "`modifier`" and MUST appear as the first optional parameter in the element function's parameter list.
+
+If the element function's content has a natural minimum size - that is, if it would ever measure with a non-zero size given constraints of minWidth and minHeight of zero - the default value of the `modifier` parameter MUST be `Modifier` - the `Modifier` type's `companion object` that represents the empty `Modifier`. Element functions without a measurable content size (e.g. Canvas, which draws arbitrary user content in the size available) MAY require the `modifier` parameter and omit the default value.
+
+Element functions MUST provide their modifier parameter to the Compose UI node they emit by passing it to the root element function they call. If the element function directly emits a Compose UI layout node, the modifier MUST be provided to the node.
+
+Element functions MAY concatenate additional modifiers to the **end** of the received `modifier` parameter before passing the concatenated modifier chain to the Compose UI node they emit.
+
+Element functions MUST NOT concatenate additional modifiers to the **beginning** of the received modifier parameter before passing the concatenated modifier chain to the Compose UI node they emit.
+
+#### Why?
+
+Modifiers are the standard means of adding external behavior to an element in Compose UI and allow common behavior to be factored out of individual or base element API surfaces. This allows element APIs to be smaller and more focused, as modifiers are used to decorate those elements with standard behavior.
+
+An element function that does not accept a modifier in this standard way does not permit this decoration and motivates consuming code to wrap a call to the element function in an additional Compose UI layout such that the desired modifier can be applied to the wrapper layout instead. This does not prevent the developer behavior of modifying the element, and forces them to write more inefficient UI code with a deeper tree structure to achieve their desired result.
+
+Modifiers occupy the first optional parameter slot to set a consistent expectation for developers that they can always provide a modifier as the final positional parameter to an element call for any given element's common case.
+
+See the Compose UI modifiers section below for more details.
+
+#### Do
+
+```kotlin
+@Composable
+fun FancyButton(
+    text: String,
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier
+) = Text(
+    text = text,
+    modifier = modifier.surface(elevation = 4.dp)
+        .clickable(onClick)
+        .padding(horizontal = 32.dp, vertical = 16.dp)
+)
+```
+
+## Compose UI layouts
+
+A Compose UI element that accepts one or more `@Composable` function parameters is called a _layout_.
+
+Example:
+
+```kotlin
+@Composable
+fun SimpleRow(
+    modifier: Modifier = Modifier,
+    content: @Composable () -> Unit
+) {
+```
+
+**Jetpack Compose framework development and Library development** MUST follow all guidelines in this section.
+
+**Jetpack Compose app development** SHOULD follow all guidelines in this section.
+
+Layout functions SHOULD use the name "`content`" for a `@Composable` function parameter if they accept only one `@Composable` function parameter.
+
+Layout functions SHOULD use the name "`content`" for their primary or most common `@Composable` function parameter if they accept more than one `@Composable` function parameter.
+
+Layout functions SHOULD place their primary or most common `@Composable` function parameter in the last parameter position to permit the use of Kotlin's trailing lambda syntax for that parameter.
+
+## Compose UI modifiers
+
+A `Modifier` is an immutable, ordered collection of objects that implement the `Modifier.Element` interface. Modifiers are universal decorators for Compose UI elements that may be used to implement and add cross-cutting behavior to elements in an opaque and encapsulated manner. Examples of modifiers include altering element sizing and padding, drawing content beneath or overlapping the element, or listening to touch events within the UI element's bounding box.
+
+**Jetpack Compose framework development and Library development** MUST follow all guidelines in this section.
+
+### Modifier factory functions
+
+Modifier chains are constructed using a fluent builder syntax expressed as Kotlin extension functions that act as factories.
+
+Example:
+
+```kotlin
+Modifier.preferredSize(50.dp)
+    .backgroundColor(Color.Blue)
+    .padding(10.dp)
+```
+
+Modifier APIs MUST NOT expose their Modifier.Element interface implementation types.
+
+Modifier APIs MUST be exposed as factory functions following this style:
+
+```kotlin
+fun Modifier.myModifier(
+    param1: ...,
+    paramN: ...
+): Modifier = then(MyModifierImpl(param1, ... paramN))
+```
+
+### Composed modifiers
+
+Modifiers that must take part in composition (for example, to read `CompositionLocal` values, maintain element-specific instance state or manage object lifetimes) can use the `Modifier.composed {}` API to create a modifier that is a modifier instance factory:
+
+```kotlin
+fun Modifier.myModifier(): Modifier = composed {
+    val color = LocalTheme.current.specialColor
+    backgroundColor(color)
+}
+```
+
+Composed modifiers are composed at each point of application to an element; the same composed modifier may be provided to multiple elements and each will have its own composition state:
+
+```kotlin
+fun Modifier.modifierWithState(): Modifier = composed {
+    val elementSpecificState = remember { MyModifierState() }
+    MyModifier(elementSpecificState)
+}
+
+// ...
+val myModifier = someModifiers.modifierWithState()
+
+Text("Hello", modifier = myModifier)
+Text("World", modifier = myModifier)
+```
+
+As a result, **Jetpack Compose framework development and Library development** SHOULD use `Modifier.composed {}` to implement composition-aware modifiers, and SHOULD NOT declare modifier extension factory functions as `@Composable` functions themselves.
+
+#### Why
+
+Composed modifiers may be created outside of composition, shared across elements, and declared as top-level constants, making them more flexible than modifiers that can only be created via a `@Composable` function call, and easier to avoid accidentally sharing state across elements.
+
+### Layout-scoped modifiers
+
+Android's View system has the concept of LayoutParams - a type of object stored opaquely with a ViewGroup's child view that provides layout instructions specific to the ViewGroup that will measure and position it.
+
+Compose UI modifiers afford a related pattern using `ParentDataModifier` and receiver scope objects for layout content functions:
+
+#### Example
+
+```kotlin
+@Stable
+interface WeightScope {
+    fun Modifier.weight(weight: Float): Modifier
+}
+
+@Composable
+fun WeightedRow(
+    modifier: Modifier = Modifier,
+    content: @Composable WeightScope.() -> Unit
+) {
+// ...
+
+// Usage:
+WeightedRow {
+    Text("Hello", Modifier.weight(1f))
+    Text("World", Modifier.weight(2f))
+}
+```
+
+**Jetpack Compose framework development and library development** SHOULD use scoped modifier factory functions to provide parent data modifiers specific to a parent layout composable.
+
+# Compose API design patterns
+
+This section outlines patterns for addressing common use cases when designing a Jetpack Compose API.
+
+## Prefer stateless and controlled @Composable functions
+
+In this context, "stateless" refers to `@Composable` functions that retain no state of their own, but instead accept external state parameters that are owned and provided by the caller. "Controlled" refers to the idea that the caller has full control over the state provided to the composable.
+
+### Do
+
+```kotlin
+@Composable
+fun Checkbox(
+    isChecked: Boolean,
+    onToggle: () -> Unit
+) {
+// ...
+
+// Usage: (caller mutates optIn and owns the source of truth)
+Checkbox(
+    myState.optIn,
+    onToggle = { myState.optIn = !myState.optIn }
+)
+```
+
+### Don't
+
+```kotlin
+@Composable
+fun Checkbox(
+    initialValue: Boolean,
+    onChecked: (Boolean) -> Unit
+) {
+    var checkedState by remember { mutableStateOf(initialValue) }
+// ...
+
+// Usage: (Checkbox owns the checked state, caller notified of changes)
+// Caller cannot easily implement a validation policy.
+Checkbox(false, onToggled = { callerCheckedState = it })
+```
+
+## Separate state and events
+
+Compose's `mutableStateOf()` value holders are observable through the `Snapshot` system and can notify observers of changes. This is the primary mechanism for requesting recomposition, relayout, or redraw of a Compose UI. Working effectively with observable state requires acknowledging the distinction between _state_ and _events_.
+
+An observable _event_ happens at a point in time and is discarded. All registered observers at the time the event occurred are notified. All individual events in a stream are assumed to be relevant and may build on one another; repeated equal events have meaning and therefore a registered observer must observe all events without skipping.
+
+Observable _state_ raises change _events_ when the state changes from one value to a new, unequal value. State change events are _conflated;_ only the most recent state matters. Observers of state changes must therefore be _idempotent;_ given the same state value the observer should produce the same result. It is valid for a state observer to both skip intermediate states as well as run multiple times for the same state and the result should be the same.
+
+Compose operates on _state_ as input, not _events_. Composable functions are _state observers_ where both the function parameters and any `mutableStateOf()` value holders that are read during execution are inputs.
+
+## Hoisted state types
+
+A pattern of stateless parameters and multiple event callback parameters will eventually reach a point of scale where it becomes unwieldy. As a composable function's parameter list grows it may become appropriate to factor a collection of state and callbacks into an interface, allowing a caller to provide a cohesive policy object as a unit.
+
+### Before
+
+```kotlin
+@Composable
+fun VerticalScroller(
+    scrollPosition: Int,
+    scrollRange: Int,
+    onScrollPositionChange: (Int) -> Unit,
+    onScrollRangeChange: (Int) -> Unit
+) {
+```
+
+### After
+
+```kotlin
+@Stable
+interface VerticalScrollerState {
+    var scrollPosition: Int
+    var scrollRange: Int
+}
+
+@Composable
+fun VerticalScroller(
+    verticalScrollerState: VerticalScrollerState
+) {
+```
+
+In the example above, an implementation of `VerticalScrollerState` is able to use custom get/set behaviors of the related `var` properties to apply policy or delegate storage of the state itself elsewhere.
+
+**Jetpack Compose framework and Library development** SHOULD declare hoisted state types for collecting and grouping interrelated policy. The VerticalScrollerState example above illustrates such a dependency between the scrollPosition and scrollRange properties; to maintain internal consistency such a state object should clamp scrollPosition into the valid range during set attempts. (Or otherwise report an error.) These properties should be grouped as handling their consistency involves handling all of them together.
+
+**Jetpack Compose framework and Library development** SHOULD declare hoisted state types as `@Stable` and correctly implement the `@Stable` contract.
+
+**Jetpack Compose framework and Library development** SHOULD name hoisted state types that are specific to a given composable function as the composable function's name suffixed by, "`State`".
+
+## Default policies through hoisted state objects
+
+Custom implementations or even external ownership of these policy objects are often not required. By using Kotlin's default arguments, Compose's `remember {}` API, and the Kotlin "extension constructor" pattern, an API can provide a default state handling policy for simple usage while permitting more sophisticated usage when desired.
+
+### Example:
+
+```kotlin
+fun VerticalScrollerState(): VerticalScrollerState = 
+    VerticalScrollerStateImpl()
+
+private class VerticalScrollerStateImpl(
+    scrollPosition: Int = 0,
+    scrollRange: Int = 0
+) : VerticalScrollerState {
+    private var _scrollPosition by
+        mutableStateOf(scrollPosition, structuralEqualityPolicy())
+
+    override var scrollPosition: Int
+        get() = _scrollPosition
+        set(value) {
+            _scrollPosition = value.coerceIn(0, scrollRange)
+        }
+
+    private var _scrollRange by
+        mutableStateOf(scrollRange, structuralEqualityPolicy())
+
+    override var scrollRange: Int
+        get() = _scrollRange
+        set(value) {
+            require(value >= 0) { "$value must be > 0" }
+            _scrollRange = value
+            scrollPosition = scrollPosition
+        }
+}
+
+@Composable
+fun VerticalScroller(
+    verticalScrollerState: VerticalScrollerState =
+        remember { VerticalScrollerState() }
+) {
+```
+
+**Jetpack Compose framework and Library development** SHOULD declare hoisted state types as interfaces instead of abstract or open classes if they are not declared as final classes.
+
+When designing an open or abstract class to be properly extensible for these use cases it is easy to create hidden requirements of state synchronization for internal consistency that are difficult (or impossible) for an extending developer to preserve. Using an interface that can be freely implemented strongly discourages private contracts between composable functions and hoisted state objects by way of Kotlin internal-scoped properties or functionality.
+
+**Jetpack Compose framework and Library development** SHOULD provide default state implementations remembered as default arguments. State objects MAY be required parameters if the composable cannot function if the state object is not configured by the caller.
+
+**Jetpack Compose framework and Library development** MUST NOT use `null` as a sentinel indicating that the composable function should internally `remember {}` its own state. This can create accidental inconsistent or unexpected behavior if `null` has a meaningful interpretation for the caller and is provided to the composable function by mistake.
+
+### Do
+
+```kotlin
+@Composable
+fun VerticalScroller(
+    verticalScrollerState: VerticalScrollerState =
+        remember { VerticalScrollerState() }
+) {
+```
+
+### Don't
+
+```kotlin
+// Null as a default can cause unexpected behavior if the input parameter
+// changes between null and non-null.
+@Composable
+fun VerticalScroller(
+    verticalScrollerState: VerticalScrollerState? = null
+) {
+    val realState = verticalScrollerState ?:
+        remember { VerticalScrollerState() }
+```
+
+## Default hoisted state for modifiers
+
+The `Modifier.composed {}` API permits construction of a Modifier factory that will be invoked later. This permits the associated Modifier factory function to be a "regular" (non-`@Composable`) function that can be called outside of composition while still permitting the use of composition to construct a modifier implementation for each element it is applied to. This does not permit using `remember {}` as a default argument expression as the factory function itself is not `@Composable`.
+
+**Jetpack Compose framework and library development** SHOULD provide an overload of Modifier factory functions that accept hoisted state parameters that omits the hoisted state object as a means of requesting default behavior, SHOULD NOT use null as a default sentinel to request the implementation to `remember {}` an element-instanced default, and SHOULD NOT declare the Modifier factory function as `@Composable` in order to use `remember {}` in a default argument expression.
+
+### Do
+
+```kotlin
+fun Modifier.foo() = composed {
+    FooModifierImpl(remember { FooState() }, LocalBar.current)
+}
+
+fun Modifier.foo(fooState: FooState) = composed {
+    FooModifierImpl(fooState, LocalBar.current)
+}
+```
+
+### Don't
+
+```kotlin
+// Null as a default can cause unexpected behavior if the input parameter
+// changes between null and non-null.
+fun Modifier.foo(
+    fooState: FooState? = null
+) = composed {
+    FooModifierImpl(
+        fooState ?: remember { FooState() },
+        LocalBar.current
+    )
+}
+```
+
+### Don't
+
+```kotlin
+// @Composable modifier factory functions cannot be used
+// outside of composition.
+@Composable
+fun Modifier.foo(
+    fooState: FooState = remember { FooState() }
+) = composed {
+    FooModifierImpl(fooState, LocalBar.current)
+}
+```
+
+## Extensibility of hoisted state types
+
+Hoisted state types often implement policy and validation that impact behavior for a composable function that accepts it. Concrete and especially final hoisted state types imply containment and ownership of the source of truth that the state object appeals to.
+
+In extreme cases this can defeat the benefits of reactive UI API designs by creating multiple sources of truth, necessitating app code to synchronize data across multiple objects. Consider the following:
+
+```kotlin
+// Defined by another team or library
+data class PersonData(val name: String, val avatarUrl: String)
+
+class FooState {
+    val currentPersonData: PersonData
+
+    fun setPersonName(name: String)
+    fun setPersonAvatarUrl(url: String)
+}
+
+// Defined by the UI layer, by yet another team
+class BarState {
+    var name: String
+    var avatarUrl: String
+}
+
+@Composable
+fun Bar(barState: BarState) {
+```
+
+These APIs are difficult to use together because both the FooState and BarState classes want to be the source of truth for the data they represent. It is often the case that different teams, libraries, or modules do not have the option of agreeing on a single unified type for data that must be shared across systems. These designs combine to form a requirement for potentially error-prone data syncing on the part of the app developer.
+
+A more flexible approach defines both of these hoisted state types as interfaces, permitting the integrating developer to define one in terms of the other, or both in terms of a third type, preserving single source of truth in their system's state management:
+
+```kotlin
+@Stable
+interface FooState {
+    val currentPersonData: PersonData
+
+    fun setPersonName(name: String)
+    fun setPersonAvatarUrl(url: String)
+}
+
+@Stable
+interface BarState {
+    var name: String
+    var avatarUrl: String
+}
+
+class MyState(
+    name: String,
+    avatarUrl: String
+) : FooState, BarState {
+    override var name by mutableStateOf(name)
+    override var avatarUrl by mutableStateOf(avatarUrl)
+
+    override val currentPersonData: PersonData =
+        PersonData(name, avatarUrl)
+
+    override fun setPersonName(name: String) {
+        this.name = name
+    }
+
+    override fun setPersonAvatarUrl(url: String) {
+        this.avatarUrl = url
+    }
+}
+```
+
+**Jetpack Compose framework and Library development** SHOULD declare hoisted state types as interfaces to permit custom implementations. If additional standard policy enforcement is necessary, consider an abstract class.
+
+**Jetpack Compose framework and Library development** SHOULD offer a factory function for a default implementation of hoisted state types sharing the same name as the type. This preserves the same simple API for consumers as a concrete type. Example:
+
+```kotlin
+@Stable
+interface FooState {
+    // ...
+}
+
+fun FooState(): FooState = FooStateImpl(...)
+
+private class FooStateImpl(...) : FooState {
+    // ...
+}
+
+// Usage
+val state = remember { FooState() }
+```
+
+**App development** SHOULD prefer simpler concrete types until the abstraction provided by an interface proves necessary. When it does, adding a factory function for a default implementation as outlined above is a source-compatible change that does not require refactoring of usage sites.
+
diff --git a/compose/foundation/foundation-layout/build.gradle b/compose/foundation/foundation-layout/build.gradle
index 891e8a2..30c0190a 100644
--- a/compose/foundation/foundation-layout/build.gradle
+++ b/compose/foundation/foundation-layout/build.gradle
@@ -16,7 +16,7 @@
 
 import androidx.build.AndroidXUiPlugin
 import androidx.build.LibraryGroups
-import androidx.build.Publish
+import androidx.build.LibraryType
 
 import static androidx.build.dependencies.DependenciesKt.*
 
@@ -106,7 +106,7 @@
 
 androidx {
     name = "Compose Layouts"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.FOUNDATION
     inceptionYear = "2019"
     description = "Compose layout implementations"
diff --git a/compose/foundation/foundation-layout/integration-tests/layout-demos/lint-baseline.xml b/compose/foundation/foundation-layout/integration-tests/layout-demos/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/foundation/foundation-layout/integration-tests/layout-demos/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/foundation/foundation-layout/lint-baseline.xml b/compose/foundation/foundation-layout/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/foundation/foundation-layout/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/foundation/foundation-layout/samples/lint-baseline.xml b/compose/foundation/foundation-layout/samples/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/foundation/foundation-layout/samples/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/foundation/foundation/api/1.0.0-beta02.txt b/compose/foundation/foundation/api/1.0.0-beta02.txt
index 76b8d5b..bff54f1 100644
--- a/compose/foundation/foundation/api/1.0.0-beta02.txt
+++ b/compose/foundation/foundation/api/1.0.0-beta02.txt
@@ -108,6 +108,9 @@
     property public final androidx.compose.runtime.saveable.Saver<androidx.compose.foundation.ScrollState,?> Saver;
   }
 
+  public final class TempListUtilsKt {
+  }
+
 }
 
 package androidx.compose.foundation.gestures {
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 76b8d5b..bff54f1 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -108,6 +108,9 @@
     property public final androidx.compose.runtime.saveable.Saver<androidx.compose.foundation.ScrollState,?> Saver;
   }
 
+  public final class TempListUtilsKt {
+  }
+
 }
 
 package androidx.compose.foundation.gestures {
diff --git a/compose/foundation/foundation/api/public_plus_experimental_1.0.0-beta02.txt b/compose/foundation/foundation/api/public_plus_experimental_1.0.0-beta02.txt
index 058fea0..d9e6a73 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_1.0.0-beta02.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_1.0.0-beta02.txt
@@ -116,6 +116,9 @@
     property public final androidx.compose.runtime.saveable.Saver<androidx.compose.foundation.ScrollState,?> Saver;
   }
 
+  public final class TempListUtilsKt {
+  }
+
 }
 
 package androidx.compose.foundation.gestures {
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index 058fea0..d9e6a73 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -116,6 +116,9 @@
     property public final androidx.compose.runtime.saveable.Saver<androidx.compose.foundation.ScrollState,?> Saver;
   }
 
+  public final class TempListUtilsKt {
+  }
+
 }
 
 package androidx.compose.foundation.gestures {
diff --git a/compose/foundation/foundation/api/restricted_1.0.0-beta02.txt b/compose/foundation/foundation/api/restricted_1.0.0-beta02.txt
index 76b8d5b..bff54f1 100644
--- a/compose/foundation/foundation/api/restricted_1.0.0-beta02.txt
+++ b/compose/foundation/foundation/api/restricted_1.0.0-beta02.txt
@@ -108,6 +108,9 @@
     property public final androidx.compose.runtime.saveable.Saver<androidx.compose.foundation.ScrollState,?> Saver;
   }
 
+  public final class TempListUtilsKt {
+  }
+
 }
 
 package androidx.compose.foundation.gestures {
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 76b8d5b..bff54f1 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -108,6 +108,9 @@
     property public final androidx.compose.runtime.saveable.Saver<androidx.compose.foundation.ScrollState,?> Saver;
   }
 
+  public final class TempListUtilsKt {
+  }
+
 }
 
 package androidx.compose.foundation.gestures {
diff --git a/compose/foundation/foundation/build.gradle b/compose/foundation/foundation/build.gradle
index e991a27..80a5550 100644
--- a/compose/foundation/foundation/build.gradle
+++ b/compose/foundation/foundation/build.gradle
@@ -17,7 +17,7 @@
 
 import androidx.build.AndroidXUiPlugin
 import androidx.build.LibraryGroups
-import androidx.build.Publish
+import androidx.build.LibraryType
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 import static androidx.build.dependencies.DependenciesKt.*
@@ -167,7 +167,7 @@
 
 androidx {
     name = "Compose Foundation"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.FOUNDATION
     inceptionYear = "2018"
     description = "Higher level abstractions of the Compose UI primitives. This library is design system agnostic, providing the high-level building blocks for both application and design-system developers"
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/lint-baseline.xml b/compose/foundation/foundation/integration-tests/foundation-demos/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/foundation/foundation/integration-tests/foundation-demos/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/foundation/foundation/lint-baseline.xml b/compose/foundation/foundation/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/foundation/foundation/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/foundation/foundation/samples/lint-baseline.xml b/compose/foundation/foundation/samples/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/foundation/foundation/samples/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
index 6a442fd..1016fbc 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyDslSamples.kt
@@ -19,6 +19,7 @@
 import androidx.annotation.Sampled
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.lazy.LazyColumn
@@ -79,7 +80,7 @@
 fun StickyHeaderSample() {
     val sections = listOf("A", "B", "C", "D", "E", "F", "G")
 
-    LazyColumn {
+    LazyColumn(reverseLayout = true, contentPadding = PaddingValues(6.dp)) {
         sections.forEach { section ->
             stickyHeader {
                 Text(
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/DraggableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/DraggableTest.kt
index b83b957..8ef8326 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/DraggableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/DraggableTest.kt
@@ -448,6 +448,7 @@
             assertThat(modifier.inspectableElements.map { it.name }.asIterable()).containsExactly(
                 "orientation",
                 "enabled",
+                "canDrag",
                 "reverseDirection",
                 "interactionSource",
                 "startDragImmediately",
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 fef0896..bcc20f7 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
@@ -17,6 +17,7 @@
 package androidx.compose.foundation
 
 import androidx.compose.animation.core.ManualFrameClock
+import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.animateScrollBy
 import androidx.compose.foundation.gestures.ScrollableState
 import androidx.compose.foundation.gestures.scrollable
@@ -37,6 +38,7 @@
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.foundation.interaction.DragInteraction
 import androidx.compose.foundation.interaction.Interaction
 import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -147,6 +149,61 @@
 
     @Test
     @OptIn(ExperimentalTestApi::class)
+    fun scrollable_horizontalScroll_reverse() = runBlockingWithManualClock { clock ->
+        var total = 0f
+        val controller = ScrollableState(
+            consumeScrollDelta = {
+                total += it
+                it
+            }
+        )
+        setScrollableContent {
+            Modifier.scrollable(
+                reverseDirection = true,
+                state = controller,
+                orientation = Orientation.Horizontal
+            )
+        }
+        rule.onNodeWithTag(scrollableBoxTag).performGesture {
+            this.swipe(
+                start = this.center,
+                end = Offset(this.center.x + 100f, this.center.y),
+                durationMillis = 100
+            )
+        }
+        advanceClockWhileAwaitersExist(clock)
+
+        val lastTotal = rule.runOnIdle {
+            assertThat(total).isLessThan(0)
+            total
+        }
+        rule.onNodeWithTag(scrollableBoxTag).performGesture {
+            this.swipe(
+                start = this.center,
+                end = Offset(this.center.x, this.center.y + 100f),
+                durationMillis = 100
+            )
+        }
+        advanceClockWhileAwaitersExist(clock)
+
+        rule.runOnIdle {
+            assertThat(total).isEqualTo(lastTotal)
+        }
+        rule.onNodeWithTag(scrollableBoxTag).performGesture {
+            this.swipe(
+                start = this.center,
+                end = Offset(this.center.x - 100f, this.center.y),
+                durationMillis = 100
+            )
+        }
+        advanceClockWhileAwaitersExist(clock)
+        rule.runOnIdle {
+            assertThat(total).isLessThan(0.01f)
+        }
+    }
+
+    @Test
+    @OptIn(ExperimentalTestApi::class)
     fun scrollable_verticalScroll() = runBlockingWithManualClock { clock ->
         var total = 0f
         val controller = ScrollableState(
@@ -201,6 +258,61 @@
 
     @Test
     @OptIn(ExperimentalTestApi::class)
+    fun scrollable_verticalScroll_reversed() = runBlockingWithManualClock { clock ->
+        var total = 0f
+        val controller = ScrollableState(
+            consumeScrollDelta = {
+                total += it
+                it
+            }
+        )
+        setScrollableContent {
+            Modifier.scrollable(
+                reverseDirection = true,
+                state = controller,
+                orientation = Orientation.Vertical
+            )
+        }
+        rule.onNodeWithTag(scrollableBoxTag).performGesture {
+            this.swipe(
+                start = this.center,
+                end = Offset(this.center.x, this.center.y + 100f),
+                durationMillis = 100
+            )
+        }
+        advanceClockWhileAwaitersExist(clock)
+
+        val lastTotal = rule.runOnIdle {
+            assertThat(total).isLessThan(0)
+            total
+        }
+        rule.onNodeWithTag(scrollableBoxTag).performGesture {
+            this.swipe(
+                start = this.center,
+                end = Offset(this.center.x + 100f, this.center.y),
+                durationMillis = 100
+            )
+        }
+        advanceClockWhileAwaitersExist(clock)
+
+        rule.runOnIdle {
+            assertThat(total).isEqualTo(lastTotal)
+        }
+        rule.onNodeWithTag(scrollableBoxTag).performGesture {
+            this.swipe(
+                start = this.center,
+                end = Offset(this.center.x, this.center.y - 100f),
+                durationMillis = 100
+            )
+        }
+        advanceClockWhileAwaitersExist(clock)
+        rule.runOnIdle {
+            assertThat(total).isLessThan(0.01f)
+        }
+    }
+
+    @Test
+    @OptIn(ExperimentalTestApi::class)
     fun scrollable_disabledWontCallLambda() = runBlockingWithManualClock { clock ->
         val enabled = mutableStateOf(true)
         var total = 0f
@@ -874,6 +986,196 @@
     }
 
     @Test
+    fun scrollable_flingBehaviourCalled_whenVelocity0() {
+        var total = 0f
+        val controller = ScrollableState(
+            consumeScrollDelta = {
+                total += it
+                it
+            }
+        )
+        var flingCalled = 0
+        var flingVelocity: Float = Float.MAX_VALUE
+        val flingBehaviour = object : FlingBehavior {
+            override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
+                flingCalled++
+                flingVelocity = initialVelocity
+                return 0f
+            }
+        }
+        setScrollableContent {
+            Modifier.scrollable(
+                state = controller,
+                flingBehavior = flingBehaviour,
+                orientation = Orientation.Horizontal
+            )
+        }
+        rule.onNodeWithTag(scrollableBoxTag).performGesture {
+            down(this.center)
+            moveBy(Offset(115f, 0f))
+            up()
+        }
+        assertThat(flingCalled).isEqualTo(1)
+        assertThat(flingVelocity).isEqualTo(0f)
+    }
+
+    @Test
+    fun scrollable_flingBehaviourCalled() {
+        var total = 0f
+        val controller = ScrollableState(
+            consumeScrollDelta = {
+                total += it
+                it
+            }
+        )
+        var flingCalled = 0
+        var flingVelocity: Float = Float.MAX_VALUE
+        val flingBehaviour = object : FlingBehavior {
+            override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
+                flingCalled++
+                flingVelocity = initialVelocity
+                return 0f
+            }
+        }
+        setScrollableContent {
+            Modifier.scrollable(
+                state = controller,
+                flingBehavior = flingBehaviour,
+                orientation = Orientation.Horizontal
+            )
+        }
+        rule.onNodeWithTag(scrollableBoxTag).performGesture {
+            swipeWithVelocity(
+                this.center,
+                this.center + Offset(115f, 0f),
+                endVelocity = 1000f
+            )
+        }
+        assertThat(flingCalled).isEqualTo(1)
+        assertThat(flingVelocity).isWithin(5f).of(1000f)
+    }
+
+    @Test
+    fun scrollable_flingBehaviourCalled_reversed() {
+        var total = 0f
+        val controller = ScrollableState(
+            consumeScrollDelta = {
+                total += it
+                it
+            }
+        )
+        var flingCalled = 0
+        var flingVelocity: Float = Float.MAX_VALUE
+        val flingBehaviour = object : FlingBehavior {
+            override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
+                flingCalled++
+                flingVelocity = initialVelocity
+                return 0f
+            }
+        }
+        setScrollableContent {
+            Modifier.scrollable(
+                state = controller,
+                reverseDirection = true,
+                flingBehavior = flingBehaviour,
+                orientation = Orientation.Horizontal
+            )
+        }
+        rule.onNodeWithTag(scrollableBoxTag).performGesture {
+            swipeWithVelocity(
+                this.center,
+                this.center + Offset(115f, 0f),
+                endVelocity = 1000f
+            )
+        }
+        assertThat(flingCalled).isEqualTo(1)
+        assertThat(flingVelocity).isWithin(5f).of(-1000f)
+    }
+
+    @Test
+    fun scrollable_flingBehaviourCalled_correctScope() {
+        var total = 0f
+        val controller = ScrollableState(
+            consumeScrollDelta = {
+                total += it
+                it
+            }
+        )
+        val flingBehaviour = object : FlingBehavior {
+            override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
+                scrollBy(123f)
+                return 0f
+            }
+        }
+        setScrollableContent {
+            Modifier.scrollable(
+                state = controller,
+                flingBehavior = flingBehaviour,
+                orientation = Orientation.Horizontal
+            )
+        }
+        rule.onNodeWithTag(scrollableBoxTag).performGesture {
+            down(center)
+            moveBy(Offset(x = 100f, y = 0f))
+        }
+
+        val prevTotal = rule.runOnIdle {
+            assertThat(total).isGreaterThan(0f)
+            total
+        }
+
+        rule.onNodeWithTag(scrollableBoxTag).performGesture {
+            up()
+        }
+
+        rule.runOnIdle {
+            assertThat(total).isEqualTo(prevTotal + 123)
+        }
+    }
+
+    @Test
+    fun scrollable_flingBehaviourCalled_reversed_correctScope() {
+        var total = 0f
+        val controller = ScrollableState(
+            consumeScrollDelta = {
+                total += it
+                it
+            }
+        )
+        val flingBehaviour = object : FlingBehavior {
+            override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
+                scrollBy(123f)
+                return 0f
+            }
+        }
+        setScrollableContent {
+            Modifier.scrollable(
+                state = controller,
+                reverseDirection = true,
+                flingBehavior = flingBehaviour,
+                orientation = Orientation.Horizontal
+            )
+        }
+        rule.onNodeWithTag(scrollableBoxTag).performGesture {
+            down(center)
+            moveBy(Offset(x = 100f, y = 0f))
+        }
+
+        val prevTotal = rule.runOnIdle {
+            assertThat(total).isLessThan(0f)
+            total
+        }
+
+        rule.onNodeWithTag(scrollableBoxTag).performGesture {
+            up()
+        }
+
+        rule.runOnIdle {
+            assertThat(total).isEqualTo(prevTotal + 123)
+        }
+    }
+
+    @Test
     fun testInspectorValue() {
         val controller = ScrollableState(
             consumeScrollDelta = { it }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyArrangementsTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyArrangementsTest.kt
index 12eeeb0..c556da3 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyArrangementsTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyArrangementsTest.kt
@@ -19,6 +19,7 @@
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalLayoutDirection
@@ -44,6 +45,7 @@
     val rule = createComposeRule()
 
     private var itemSize: Dp = Dp.Infinity
+    private var smallerItemSize: Dp = Dp.Infinity
     private var containerSize: Dp = Dp.Infinity
 
     @Before
@@ -51,6 +53,9 @@
         with(rule.density) {
             itemSize = 50.toDp()
         }
+        with(rule.density) {
+            smallerItemSize = 40.toDp()
+        }
         containerSize = itemSize * 5
     }
 
@@ -279,12 +284,12 @@
                 modifier = Modifier.requiredSize(containerSize)
             ) {
                 items(2) {
-                    Box(Modifier.requiredSize(itemSize).testTag(it.toString()))
+                    Item(it)
                 }
             }
         }
 
-        assertArrangementForTwoItems(Arrangement.Bottom, reversedItemsOrder = true)
+        assertArrangementForTwoItems(Arrangement.Bottom, reverseLayout = true)
     }
 
     @Test
@@ -295,13 +300,13 @@
                 modifier = Modifier.requiredSize(containerSize)
             ) {
                 items(2) {
-                    Box(Modifier.requiredSize(itemSize).testTag(it.toString()))
+                    Item(it)
                 }
             }
         }
 
         assertArrangementForTwoItems(
-            Arrangement.End, LayoutDirection.Ltr, reversedItemsOrder = true
+            Arrangement.End, LayoutDirection.Ltr, reverseLayout = true
         )
     }
 
@@ -312,7 +317,7 @@
                 modifier = Modifier.requiredSize(containerSize)
             ) {
                 items(2) {
-                    Box(Modifier.requiredSize(itemSize).testTag(it.toString()))
+                    Item(it)
                 }
             }
         }
@@ -326,24 +331,34 @@
                     modifier = Modifier.requiredSize(containerSize)
                 ) {
                     items(2) {
-                        Box(Modifier.requiredSize(itemSize).testTag(it.toString()))
+                        Item(it)
                     }
                 }
             }
         }
     }
 
+    @Composable
+    fun Item(index: Int) {
+        require(index < 2)
+        val size = if (index == 0) itemSize else smallerItemSize
+        Box(Modifier.requiredSize(size).testTag(index.toString()))
+    }
+
     fun assertArrangementForTwoItems(
         arrangement: Arrangement.Vertical,
-        reversedItemsOrder: Boolean = false
+        reverseLayout: Boolean = false
     ) {
         with(rule.density) {
-            val sizes = IntArray(2) { itemSize.roundToPx() }
+            val sizes = IntArray(2) {
+                val index = if (reverseLayout) if (it == 0) 1 else 0 else it
+                if (index == 0) itemSize.roundToPx() else smallerItemSize.roundToPx()
+            }
             val outPositions = IntArray(2) { 0 }
             with(arrangement) { arrange(containerSize.roundToPx(), sizes, outPositions) }
 
             outPositions.forEachIndexed { index, position ->
-                val realIndex = if (reversedItemsOrder) if (index == 0) 1 else 0 else index
+                val realIndex = if (reverseLayout) if (index == 0) 1 else 0 else index
                 rule.onNodeWithTag("$realIndex")
                     .assertTopPositionInRootIsEqualTo(position.toDp())
             }
@@ -353,21 +368,25 @@
     fun assertArrangementForTwoItems(
         arrangement: Arrangement.Horizontal,
         layoutDirection: LayoutDirection,
-        reversedItemsOrder: Boolean = false
+        reverseLayout: Boolean = false
     ) {
         with(rule.density) {
-            val sizes = IntArray(2) { itemSize.roundToPx() }
+            val sizes = IntArray(2) {
+                val index = if (reverseLayout) if (it == 0) 1 else 0 else it
+                if (index == 0) itemSize.roundToPx() else smallerItemSize.roundToPx()
+            }
             val outPositions = IntArray(2) { 0 }
             with(arrangement) {
                 arrange(containerSize.roundToPx(), sizes, layoutDirection, outPositions)
             }
 
             outPositions.forEachIndexed { index, position ->
-                val realIndex = if (reversedItemsOrder) if (index == 0) 1 else 0 else index
+                val realIndex = if (reverseLayout) if (index == 0) 1 else 0 else index
+                val size = if (realIndex == 0) itemSize else smallerItemSize
                 val expectedPosition = if (layoutDirection == LayoutDirection.Ltr) {
                     position.toDp()
                 } else {
-                    containerSize - position.toDp() - itemSize
+                    containerSize - position.toDp() - size
                 }
                 rule.onNodeWithTag("$realIndex")
                     .assertLeftPositionInRootIsEqualTo(expectedPosition)
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListLayoutInfoTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListLayoutInfoTest.kt
index 677e345..9f259df 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListLayoutInfoTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListLayoutInfoTest.kt
@@ -28,18 +28,26 @@
 import androidx.compose.ui.test.junit4.createComposeRule
 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 com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.runBlocking
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
 
 @MediumTest
-@RunWith(AndroidJUnit4::class)
-class LazyListLayoutInfoTest {
+@RunWith(Parameterized::class)
+class LazyListLayoutInfoTest(
+    private val reverseLayout: Boolean
+) {
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "reverseLayout={0}")
+        fun initParameters(): Array<Any> = arrayOf(false, true)
+    }
 
     @get:Rule
     val rule = createComposeRule()
@@ -60,6 +68,7 @@
         rule.setContent {
             LazyColumn(
                 state = rememberLazyListState().also { state = it },
+                reverseLayout = reverseLayout,
                 modifier = Modifier.requiredSize(itemSizeDp * 3.5f)
             ) {
                 items((0..5).toList()) {
@@ -79,6 +88,7 @@
         rule.setContent {
             LazyColumn(
                 state = rememberLazyListState().also { state = it },
+                reverseLayout = reverseLayout,
                 modifier = Modifier.requiredSize(itemSizeDp * 3.5f)
             ) {
                 items((0..5).toList()) {
@@ -101,6 +111,7 @@
         rule.setContent {
             LazyColumn(
                 state = rememberLazyListState().also { state = it },
+                reverseLayout = reverseLayout,
                 verticalArrangement = Arrangement.spacedBy(itemSizeDp),
                 modifier = Modifier.requiredSize(itemSizeDp * 3.5f)
             ) {
@@ -126,6 +137,7 @@
         rule.setContent {
             LazyColumn(
                 state = rememberLazyListState().also { state = it },
+                reverseLayout = reverseLayout,
                 modifier = Modifier.requiredSize(itemSizeDp * 3.5f)
             ) {
                 items((0..5).toList()) {
@@ -160,6 +172,7 @@
         }
         rule.setContent {
             LazyColumn(
+                reverseLayout = reverseLayout,
                 state = rememberLazyListState().also { state = it }
             ) {
                 item {
@@ -188,6 +201,7 @@
         lateinit var state: LazyListState
         rule.setContent {
             LazyColumn(
+                reverseLayout = reverseLayout,
                 state = rememberLazyListState().also { state = it }
             ) {
                 items((0 until count).toList()) {
@@ -214,6 +228,7 @@
         rule.setContent {
             LazyColumn(
                 Modifier.requiredSize(sizeDp),
+                reverseLayout = reverseLayout,
                 state = rememberLazyListState().also { state = it }
             ) {
                 items((0..3).toList()) {
@@ -241,6 +256,7 @@
             LazyColumn(
                 Modifier.requiredSize(sizeDp),
                 contentPadding = PaddingValues(top = topPaddingDp, bottom = bottomPaddingDp),
+                reverseLayout = reverseLayout,
                 state = rememberLazyListState().also { state = it }
             ) {
                 items((0..3).toList()) {
@@ -267,7 +283,8 @@
         var currentOffset = startOffset
         visibleItemsInfo.forEach {
             assertThat(it.index).isEqualTo(currentIndex)
-            assertThat(it.offset).isEqualTo(currentOffset)
+            assertWithMessage("Offset of item $currentIndex").that(it.offset)
+                .isEqualTo(currentOffset)
             assertThat(it.size).isEqualTo(expectedSize)
             currentIndex++
             currentOffset += it.size + spacing
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/shape/CornerSizeTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/shape/CornerSizeTest.kt
index 744830a..6bd5c4c 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/shape/CornerSizeTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/shape/CornerSizeTest.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.shape
 
 import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -36,24 +37,28 @@
     fun pxCorners() {
         val corner = CornerSize(24.0f)
         assertThat(corner.toPx(size, density)).isEqualTo(24.0f)
+        assertThat(corner.inspectorValue()).isEqualTo("24.0px")
     }
 
     @Test
     fun dpCorners() {
         val corner = CornerSize(5.dp)
         assertThat(corner.toPx(size, density)).isEqualTo(12.5f)
+        assertThat(corner.inspectorValue()).isEqualTo(5.dp)
     }
 
     @Test
     fun intPercentCorners() {
         val corner = CornerSize(15)
         assertThat(corner.toPx(size, density)).isEqualTo(22.5f)
+        assertThat(corner.inspectorValue()).isEqualTo("15.0%")
     }
 
     @Test
     fun zeroCorners() {
         val corner = ZeroCornerSize
         assertThat(corner.toPx(size, density)).isEqualTo(0.0f)
+        assertThat(corner.inspectorValue()).isEqualTo("ZeroCornerSize")
     }
 
     @Test
@@ -69,4 +74,6 @@
             CornerSize(8.dp)
         )
     }
+
+    private fun CornerSize.inspectorValue() = (this as InspectableValue).valueOverride
 }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegateTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegateTest.kt
index b388b1b..dcc418f 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegateTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegateTest.kt
@@ -84,18 +84,14 @@
                 val layoutCoordinates = mock<LayoutCoordinates>()
                 whenever(layoutCoordinates.isAttached).thenReturn(true)
 
+                val selectableId = 1L
                 val selectable = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = {},
+                    selectableId = selectableId,
                     coordinatesCallback = { layoutCoordinates },
                     layoutResultCallback = { layoutResult }
                 )
 
-                val selectableInvalid = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = {},
-                    coordinatesCallback = { null },
-                    layoutResultCallback = { layoutResult }
-                )
-
+                val selectableInvalidId = 2L
                 val startOffset = text.indexOf('h')
                 val endOffset = text.indexOf('o')
 
@@ -103,12 +99,12 @@
                     start = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = startOffset,
-                        selectable = selectableInvalid
+                        selectableId = selectableInvalidId
                     ),
                     end = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = endOffset,
-                        selectable = selectableInvalid
+                        selectableId = selectableInvalidId
                     ),
                     handlesCrossed = false
                 )
@@ -141,18 +137,14 @@
                 val layoutCoordinates = mock<LayoutCoordinates>()
                 whenever(layoutCoordinates.isAttached).thenReturn(true)
 
+                val selectableId = 1L
                 val selectable = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = {},
+                    selectableId = selectableId,
                     coordinatesCallback = { layoutCoordinates },
                     layoutResultCallback = { layoutResult }
                 )
 
-                val selectableInvalid = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = {},
-                    coordinatesCallback = { null },
-                    layoutResultCallback = { layoutResult }
-                )
-
+                val selectableInvalidId = 2L
                 val startOffset = text.indexOf('h')
                 val endOffset = text.indexOf('o')
 
@@ -160,12 +152,12 @@
                     start = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = startOffset,
-                        selectable = selectableInvalid
+                        selectableId = selectableInvalidId
                     ),
                     end = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = endOffset,
-                        selectable = selectableInvalid
+                        selectableId = selectableInvalidId
                     ),
                     handlesCrossed = false
                 )
@@ -199,8 +191,9 @@
                 val layoutCoordinates = mock<LayoutCoordinates>()
                 whenever(layoutCoordinates.isAttached).thenReturn(true)
 
+                val selectableId = 1L
                 val selectable = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = {},
+                    selectableId = selectableId,
                     coordinatesCallback = { layoutCoordinates },
                     layoutResultCallback = { layoutResult }
                 )
@@ -212,12 +205,12 @@
                     start = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = startOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     end = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = endOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     handlesCrossed = false
                 )
@@ -253,8 +246,9 @@
                 val layoutCoordinates = mock<LayoutCoordinates>()
                 whenever(layoutCoordinates.isAttached).thenReturn(true)
 
+                val selectableId = 1L
                 val selectable = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = {},
+                    selectableId = selectableId,
                     coordinatesCallback = { layoutCoordinates },
                     layoutResultCallback = { layoutResult }
                 )
@@ -266,12 +260,12 @@
                     start = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = startOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     end = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = endOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     handlesCrossed = true
                 )
@@ -307,8 +301,9 @@
                 val layoutCoordinates = mock<LayoutCoordinates>()
                 whenever(layoutCoordinates.isAttached).thenReturn(true)
 
+                val selectableId = 1L
                 val selectable = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = {},
+                    selectableId = selectableId,
                     coordinatesCallback = { layoutCoordinates },
                     layoutResultCallback = { layoutResult }
                 )
@@ -320,12 +315,12 @@
                     start = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = startOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     end = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = endOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     handlesCrossed = false
                 )
@@ -361,8 +356,9 @@
                 val layoutCoordinates = mock<LayoutCoordinates>()
                 whenever(layoutCoordinates.isAttached).thenReturn(true)
 
+                val selectableId = 1L
                 val selectable = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = {},
+                    selectableId = selectableId,
                     coordinatesCallback = { layoutCoordinates },
                     layoutResultCallback = { layoutResult }
                 )
@@ -374,12 +370,12 @@
                     start = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = startOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     end = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = endOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     handlesCrossed = true
                 )
@@ -417,8 +413,9 @@
                 val layoutCoordinates = mock<LayoutCoordinates>()
                 whenever(layoutCoordinates.isAttached).thenReturn(true)
 
+                val selectableId = 1L
                 val selectable = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = {},
+                    selectableId = selectableId,
                     coordinatesCallback = { layoutCoordinates },
                     layoutResultCallback = { layoutResult }
                 )
@@ -430,12 +427,12 @@
                     start = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = startOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     end = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = endOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     handlesCrossed = false
                 )
@@ -473,8 +470,9 @@
                 val layoutCoordinates = mock<LayoutCoordinates>()
                 whenever(layoutCoordinates.isAttached).thenReturn(true)
 
+                val selectableId = 1L
                 val selectable = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = {},
+                    selectableId = selectableId,
                     coordinatesCallback = { layoutCoordinates },
                     layoutResultCallback = { layoutResult }
                 )
@@ -486,12 +484,12 @@
                     start = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = startOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     end = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = endOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     handlesCrossed = true
                 )
@@ -527,8 +525,9 @@
                 val layoutCoordinates = mock<LayoutCoordinates>()
                 whenever(layoutCoordinates.isAttached).thenReturn(true)
 
+                val selectableId = 1L
                 val selectable = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = {},
+                    selectableId = selectableId,
                     coordinatesCallback = { layoutCoordinates },
                     layoutResultCallback = { layoutResult }
                 )
@@ -540,12 +539,12 @@
                     start = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = startOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     end = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = endOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     handlesCrossed = false
                 )
@@ -581,8 +580,9 @@
                 val layoutCoordinates = mock<LayoutCoordinates>()
                 whenever(layoutCoordinates.isAttached).thenReturn(true)
 
+                val selectableId = 1L
                 val selectable = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = {},
+                    selectableId = selectableId,
                     coordinatesCallback = { layoutCoordinates },
                     layoutResultCallback = { layoutResult }
                 )
@@ -594,12 +594,12 @@
                     start = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = startOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     end = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = endOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     handlesCrossed = true
                 )
@@ -635,8 +635,9 @@
                 val layoutCoordinates = mock<LayoutCoordinates>()
                 whenever(layoutCoordinates.isAttached).thenReturn(true)
 
+                val selectableId = 1L
                 val selectable = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = {},
+                    selectableId = selectableId,
                     coordinatesCallback = { layoutCoordinates },
                     layoutResultCallback = { layoutResult }
                 )
@@ -648,12 +649,12 @@
                     start = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = startOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     end = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = endOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     handlesCrossed = false
                 )
@@ -689,8 +690,9 @@
                 val layoutCoordinates = mock<LayoutCoordinates>()
                 whenever(layoutCoordinates.isAttached).thenReturn(true)
 
+                val selectableId = 1L
                 val selectable = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = {},
+                    selectableId = selectableId,
                     coordinatesCallback = { layoutCoordinates },
                     layoutResultCallback = { layoutResult }
                 )
@@ -702,12 +704,12 @@
                     start = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = startOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     end = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = endOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     handlesCrossed = true
                 )
@@ -745,8 +747,9 @@
                 val layoutCoordinates = mock<LayoutCoordinates>()
                 whenever(layoutCoordinates.isAttached).thenReturn(true)
 
+                val selectableId = 1L
                 val selectable = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = {},
+                    selectableId = selectableId,
                     coordinatesCallback = { layoutCoordinates },
                     layoutResultCallback = { layoutResult }
                 )
@@ -758,12 +761,12 @@
                     start = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = startOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     end = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = endOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     handlesCrossed = false
                 )
@@ -801,8 +804,9 @@
                 val layoutCoordinates = mock<LayoutCoordinates>()
                 whenever(layoutCoordinates.isAttached).thenReturn(true)
 
+                val selectableId = 1L
                 val selectable = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = {},
+                    selectableId = selectableId,
                     coordinatesCallback = { layoutCoordinates },
                     layoutResultCallback = { layoutResult }
                 )
@@ -814,12 +818,12 @@
                     start = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = startOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     end = Selection.AnchorInfo(
                         direction = ResolvedTextDirection.Ltr,
                         offset = endOffset,
-                        selectable = selectable
+                        selectableId = selectableId
                     ),
                     handlesCrossed = true
                 )
@@ -848,7 +852,7 @@
                 whenever(layoutCoordinates.isAttached).thenReturn(true)
 
                 val selectable = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = {},
+                    0,
                     coordinatesCallback = { layoutCoordinates },
                     layoutResultCallback = { layoutResult }
                 )
@@ -878,7 +882,7 @@
                 whenever(layoutCoordinates.isAttached).thenReturn(true)
 
                 val selectable = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = {},
+                    0,
                     coordinatesCallback = { layoutCoordinates },
                     layoutResultCallback = { layoutResult }
                 )
@@ -906,7 +910,7 @@
                 whenever(layoutCoordinates.isAttached).thenReturn(true)
 
                 val selectable = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = mock(),
+                    1,
                     coordinatesCallback = { layoutCoordinates },
                     layoutResultCallback = { layoutResult }
                 )
@@ -943,7 +947,7 @@
                 whenever(layoutCoordinates.isAttached).thenReturn(true)
 
                 val selectable = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = mock(),
+                    0,
                     coordinatesCallback = { layoutCoordinates },
                     layoutResultCallback = { layoutResult }
                 )
@@ -978,7 +982,7 @@
                 whenever(layoutCoordinates.isAttached).thenReturn(true)
 
                 val selectable = MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = mock(),
+                    0,
                     coordinatesCallback = { layoutCoordinates },
                     layoutResultCallback = { layoutResult }
                 )
@@ -1512,17 +1516,18 @@
             // "llo" is selected.
             val oldStartOffset = text.indexOf("l")
             val oldEndOffset = text.indexOf("o") + 1
-            val selectable: Selectable = mock()
+            val selectableId = 1L
+            val selectable: Selectable = mockSelectable(selectableId)
             val previousSelection = Selection(
                 start = Selection.AnchorInfo(
                     offset = oldStartOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 end = Selection.AnchorInfo(
                     offset = oldEndOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 handlesCrossed = false
             )
@@ -1568,17 +1573,18 @@
             // "\u05D0\u05D1" is selected.
             val oldStartOffset = text.indexOf("\u05D1")
             val oldEndOffset = text.length
-            val selectable: Selectable = mock()
+            val selectableId = 1L
+            val selectable: Selectable = mockSelectable(selectableId)
             val previousSelection = Selection(
                 start = Selection.AnchorInfo(
                     offset = oldStartOffset,
                     direction = ResolvedTextDirection.Rtl,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 end = Selection.AnchorInfo(
                     offset = oldEndOffset,
                     direction = ResolvedTextDirection.Rtl,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 handlesCrossed = false
             )
@@ -1630,17 +1636,18 @@
             // "llo" is selected.
             val oldStartOffset = text.indexOf("l")
             val oldEndOffset = text.indexOf("o") + 1
-            val selectable: Selectable = mock()
+            val selectableId = 1L
+            val selectable: Selectable = mockSelectable(selectableId)
             val previousSelection = Selection(
                 start = Selection.AnchorInfo(
                     offset = oldStartOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 end = Selection.AnchorInfo(
                     offset = oldEndOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 handlesCrossed = false
             )
@@ -1687,17 +1694,18 @@
             // "llo" is selected.
             val oldStartOffset = text.indexOf("o") + 1
             val oldEndOffset = text.indexOf("l")
-            val selectable: Selectable = mock()
+            val selectableId = 1L
+            val selectable: Selectable = mockSelectable(selectableId)
             val previousSelection = Selection(
                 start = Selection.AnchorInfo(
                     offset = oldStartOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 end = Selection.AnchorInfo(
                     offset = oldEndOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 handlesCrossed = true
             )
@@ -1744,17 +1752,18 @@
             // "e" is selected.
             val oldStartOffset = text.indexOf("e")
             val oldEndOffset = text.indexOf("l")
-            val selectable: Selectable = mock()
+            val selectableId = 1L
+            val selectable: Selectable = mockSelectable(selectableId)
             val previousSelection = Selection(
                 start = Selection.AnchorInfo(
                     offset = oldStartOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 end = Selection.AnchorInfo(
                     offset = oldEndOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 handlesCrossed = false
             )
@@ -1791,17 +1800,18 @@
             // "e" is selected.
             val oldStartOffset = text.indexOf("l")
             val oldEndOffset = text.indexOf("e")
-            val selectable: Selectable = mock()
+            val selectableId = 1L
+            val selectable: Selectable = mockSelectable(selectableId)
             val previousSelection = Selection(
                 start = Selection.AnchorInfo(
                     offset = oldStartOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 end = Selection.AnchorInfo(
                     offset = oldEndOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 handlesCrossed = true
             )
@@ -1838,17 +1848,18 @@
             // "d" is selected.
             val oldStartOffset = text.length - 1
             val oldEndOffset = text.length
-            val selectable: Selectable = mock()
+            val selectableId = 1L
+            val selectable: Selectable = mockSelectable(selectableId)
             val previousSelection = Selection(
                 start = Selection.AnchorInfo(
                     offset = oldStartOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 end = Selection.AnchorInfo(
                     offset = oldEndOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 handlesCrossed = false
             )
@@ -1891,17 +1902,18 @@
             // "h" is selected.
             val oldStartOffset = text.indexOf("e")
             val oldEndOffset = 0
-            val selectable: Selectable = mock()
+            val selectableId = 1L
+            val selectable: Selectable = mockSelectable(selectableId)
             val previousSelection = Selection(
                 start = Selection.AnchorInfo(
                     offset = oldStartOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 end = Selection.AnchorInfo(
                     offset = oldEndOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 handlesCrossed = true
             )
@@ -1938,17 +1950,18 @@
             // "llo" is selected.
             val oldStartOffset = text.indexOf("o") + 1
             val oldEndOffset = text.indexOf("l")
-            val selectable: Selectable = mock()
+            val selectableId = 1L
+            val selectable: Selectable = mockSelectable(selectableId)
             val previousSelection = Selection(
                 start = Selection.AnchorInfo(
                     offset = oldStartOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 end = Selection.AnchorInfo(
                     offset = oldEndOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 handlesCrossed = true
             )
@@ -1995,17 +2008,18 @@
             // "e" is selected.
             val oldStartOffset = text.indexOf("e")
             val oldEndOffset = text.indexOf("l")
-            val selectable: Selectable = mock()
+            val selectableId = 1L
+            val selectable: Selectable = mockSelectable(selectableId)
             val previousSelection = Selection(
                 start = Selection.AnchorInfo(
                     offset = oldStartOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 end = Selection.AnchorInfo(
                     offset = oldEndOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 handlesCrossed = false
             )
@@ -2042,17 +2056,18 @@
             // "e" is selected.
             val oldStartOffset = text.indexOf("l")
             val oldEndOffset = text.indexOf("e")
-            val selectable: Selectable = mock()
+            val selectableId = 1L
+            val selectable: Selectable = mockSelectable(selectableId)
             val previousSelection = Selection(
                 start = Selection.AnchorInfo(
                     offset = oldStartOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 end = Selection.AnchorInfo(
                     offset = oldEndOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 handlesCrossed = true
             )
@@ -2089,17 +2104,18 @@
             // "h" is selected.
             val oldStartOffset = 0
             val oldEndOffset = text.indexOf('e')
-            val selectable: Selectable = mock()
+            val selectableId = 1L
+            val selectable: Selectable = mockSelectable(selectableId)
             val previousSelection = Selection(
                 start = Selection.AnchorInfo(
                     offset = oldStartOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 end = Selection.AnchorInfo(
                     offset = oldEndOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 handlesCrossed = false
             )
@@ -2142,17 +2158,18 @@
             // "d" is selected.
             val oldStartOffset = text.length
             val oldEndOffset = text.length - 1
-            val selectable: Selectable = mock()
+            val selectableId = 1L
+            val selectable: Selectable = mockSelectable(selectableId)
             val previousSelection = Selection(
                 start = Selection.AnchorInfo(
                     offset = oldStartOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 end = Selection.AnchorInfo(
                     offset = oldEndOffset,
                     direction = ResolvedTextDirection.Ltr,
-                    selectable = selectable
+                    selectableId = selectableId
                 ),
                 handlesCrossed = true
             )
@@ -2387,6 +2404,12 @@
     }
 }
 
+private fun mockSelectable(selectableId: Long): Selectable {
+    val selectable: Selectable = mock()
+    whenever(selectable.selectableId).thenReturn(selectableId)
+    return selectable
+}
+
 class TestFontResourceLoader(val context: Context) : Font.ResourceLoader {
     override fun load(font: Font): Typeface {
         return when (font) {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/SelectionHandlesTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/SelectionHandlesTest.kt
index 1901667..6376464 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/SelectionHandlesTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/SelectionHandlesTest.kt
@@ -37,7 +37,6 @@
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
-import com.nhaarman.mockitokotlin2.mock
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -64,12 +63,12 @@
         start = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = 0,
-            selectable = mock()
+            selectableId = 0
         ),
         end = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = 0,
-            selectable = mock()
+            selectableId = 0
         ),
         handlesCrossed = false
     )
@@ -77,12 +76,12 @@
         start = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = 0,
-            selectable = mock()
+            selectableId = 0
         ),
         end = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = 0,
-            selectable = mock()
+            selectableId = 0
         ),
         handlesCrossed = true
     )
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Expect.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Expect.kt
index 703fdd5..ad0c1e9 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Expect.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Expect.kt
@@ -23,4 +23,10 @@
     fun set(value: V)
     fun getAndSet(value: V): V
     fun compareAndSet(expect: V, newValue: V): Boolean
+}
+
+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/src/commonMain/kotlin/androidx/compose/foundation/TempListUtils.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/TempListUtils.kt
new file mode 100644
index 0000000..02234ab
--- /dev/null
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/TempListUtils.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
+
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachIndexed
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+
+// TODO: remove these when we can add new APIs to ui-util outside of beta cycle
+
+/**
+ * Returns a list containing only elements matching the given [predicate].
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T> List<T>.fastFilter(predicate: (T) -> Boolean): List<T> {
+    contract { callsInPlace(predicate) }
+    val target = ArrayList<T>(size)
+    fastForEach {
+        if (predicate(it)) target += (it)
+    }
+    return target
+}
+
+/**
+ * Accumulates value starting with [initial] value and applying [operation] from left to right
+ * to current accumulator value and each element.
+ *
+ * Returns the specified [initial] value if the collection is empty.
+ *
+ * @param [operation] function that takes current accumulator value and an element, and calculates the next accumulator value.
+ */
+@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
+}
+
+/**
+ * Returns a list containing the results of applying the given [transform] function
+ * to each element in the original collection.
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T, R> List<T>.fastMapIndexedNotNull(
+    transform: (index: Int, T) -> R?
+): List<R> {
+    contract { callsInPlace(transform) }
+    val target = ArrayList<R>(size)
+    fastForEachIndexed { index, e ->
+        transform(index, e)?.let { target += it }
+    }
+    return target
+}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/DragGestureDetector.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/DragGestureDetector.kt
index 8d15297..f12d7de 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/DragGestureDetector.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/DragGestureDetector.kt
@@ -75,11 +75,11 @@
 
     while (true) {
         val event = awaitPointerEvent()
-        val dragEvent = event.changes.firstOrNull { it.id == pointer }!!
+        val dragEvent = event.changes.fastFirstOrNull { it.id == pointer }!!
         if (dragEvent.positionChangeConsumed()) {
             return null
         } else if (dragEvent.changedToUpIgnoreConsumed()) {
-            val otherDown = event.changes.firstOrNull { it.pressed }
+            val otherDown = event.changes.fastFirstOrNull { it.pressed }
             if (otherDown == null) {
                 // This is the last "up"
                 return null
@@ -567,9 +567,9 @@
     var pointer = pointerId
     while (true) {
         val event = awaitPointerEvent()
-        val dragEvent = event.changes.firstOrNull { it.id == pointer }!!
+        val dragEvent = event.changes.fastFirstOrNull { it.id == pointer }!!
         if (dragEvent.changedToUpIgnoreConsumed()) {
-            val otherDown = event.changes.firstOrNull { it.pressed }
+            val otherDown = event.changes.fastFirstOrNull { it.pressed }
             if (otherDown == null) {
                 // This is the last "up"
                 return dragEvent
@@ -616,11 +616,11 @@
 
     while (true) {
         val event = awaitPointerEvent()
-        val dragEvent = event.changes.firstOrNull { it.id == pointer }!!
+        val dragEvent = event.changes.fastFirstOrNull { it.id == pointer }!!
         if (dragEvent.positionChangeConsumed()) {
             return null
         } else if (dragEvent.changedToUpIgnoreConsumed()) {
-            val otherDown = event.changes.firstOrNull { it.pressed }
+            val otherDown = event.changes.fastFirstOrNull { it.pressed }
             if (otherDown == null) {
                 // This is the last "up"
                 return null
@@ -688,7 +688,7 @@
                         finished = true
                     }
                     if (!event.isPointerUp(currentDown.id)) {
-                        longPress = event.changes.firstOrNull { it.id == currentDown.id }
+                        longPress = event.changes.fastFirstOrNull { it.id == currentDown.id }
                     } else {
                         val newPressed = event.changes.fastFirstOrNull { it.pressed }
                         if (newPressed != null) {
@@ -709,4 +709,4 @@
 }
 
 private fun PointerEvent.isPointerUp(pointerId: PointerId): Boolean =
-    changes.firstOrNull { it.id == pointerId }?.pressed != true
\ No newline at end of file
+    changes.fastFirstOrNull { it.id == pointerId }?.pressed != true
\ No newline at end of file
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 67e0d67..7229530 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
@@ -168,9 +168,32 @@
     onDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit = {},
     onDragStopped: suspend CoroutineScope.(velocity: Float) -> Unit = {},
     reverseDirection: Boolean = false
+): Modifier = draggable(
+    state = state,
+    orientation = orientation,
+    enabled = enabled,
+    interactionSource = interactionSource,
+    startDragImmediately = startDragImmediately,
+    onDragStarted = onDragStarted,
+    onDragStopped = onDragStopped,
+    reverseDirection = reverseDirection,
+    canDrag = { true }
+)
+
+internal fun Modifier.draggable(
+    state: DraggableState,
+    canDrag: (PointerInputChange) -> Boolean,
+    orientation: Orientation,
+    enabled: Boolean = true,
+    interactionSource: MutableInteractionSource? = null,
+    startDragImmediately: Boolean = false,
+    onDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit = {},
+    onDragStopped: suspend CoroutineScope.(velocity: Float) -> Unit = {},
+    reverseDirection: Boolean = false
 ): Modifier = composed(
     inspectorInfo = debugInspectorInfo {
         name = "draggable"
+        properties["canDrag"] = canDrag
         properties["orientation"] = orientation
         properties["enabled"] = enabled
         properties["reverseDirection"] = reverseDirection
@@ -198,11 +221,13 @@
     val onDragStartedState = rememberUpdatedState(onDragStarted)
     val updatedDraggableState = rememberUpdatedState(state)
     val onDragStoppedState = rememberUpdatedState(onDragStopped)
+    val canDragState = rememberUpdatedState(canDrag)
     val dragBlock: suspend PointerInputScope.() -> Unit = remember {
         {
             dragForEachGesture(
                 orientation = orientationState,
                 enabled = enabledState,
+                canDrag = canDragState,
                 interactionSource = interactionSourceState,
                 dragStartInteraction = draggedInteraction,
                 reverseDirection = reverseDirectionState,
@@ -219,6 +244,7 @@
 private suspend fun PointerInputScope.dragForEachGesture(
     orientation: State<Orientation>,
     enabled: State<Boolean>,
+    canDrag: State<(PointerInputChange) -> Boolean>,
     reverseDirection: State<Boolean>,
     interactionSource: State<MutableInteractionSource?>,
     dragStartInteraction: MutableState<DragInteraction.Start?>,
@@ -260,7 +286,7 @@
             var initialDelta = 0f
             val startEvent = awaitPointerEventScope {
                 val down = awaitFirstDown(requireUnconsumed = false)
-                if (!enabled.value) {
+                if (!enabled.value || !canDrag.value.invoke(down)) {
                     null
                 } else if (startDragImmediately.value) {
                     // since we start immediately we don't wait for slop and set initial delta to 0
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
index 319f91f..21b9c10 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
@@ -22,12 +22,8 @@
 import androidx.compose.animation.defaultDecayAnimationSpec
 import androidx.compose.foundation.MutatePriority
 import androidx.compose.foundation.gestures.Orientation.Horizontal
-import androidx.compose.foundation.gestures.Orientation.Vertical
-import androidx.compose.foundation.interaction.DragInteraction
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.State
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -39,17 +35,9 @@
 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.pointer.PointerInputChange
-import androidx.compose.ui.input.pointer.PointerInputScope
 import androidx.compose.ui.input.pointer.PointerType
-import androidx.compose.ui.input.pointer.consumePositionChange
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.input.pointer.positionChange
-import androidx.compose.ui.input.pointer.util.VelocityTracker
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.unit.Velocity
-import kotlinx.coroutines.CancellationException
-import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 import kotlin.math.abs
 
@@ -144,181 +132,30 @@
     flingBehavior: FlingBehavior?,
     enabled: Boolean
 ): Modifier {
-    val draggedInteraction = remember { mutableStateOf<DragInteraction.Start?>(null) }
-    DisposableEffect(interactionSource) {
-        onDispose {
-            draggedInteraction.value?.let { interaction ->
-                interactionSource?.tryEmit(DragInteraction.Cancel(interaction))
-                draggedInteraction.value = null
-            }
-        }
-    }
-
+    val fling = flingBehavior ?: ScrollableDefaults.flingBehavior()
     val nestedScrollDispatcher = remember { mutableStateOf(NestedScrollDispatcher()) }
     val scrollLogic = rememberUpdatedState(
-        ScrollingLogic(
-            orientation,
-            reverseDirection,
-            nestedScrollDispatcher,
-            controller,
-            flingBehavior ?: ScrollableDefaults.flingBehavior()
-        )
+        ScrollingLogic(orientation, reverseDirection, nestedScrollDispatcher, controller, fling)
     )
     val nestedScrollConnection = remember { scrollableNestedScrollConnection(scrollLogic) }
-    val orientationState = rememberUpdatedState(orientation)
-    val enabledState = rememberUpdatedState(enabled)
-    val controllerState = rememberUpdatedState(controller)
-    val interactionSourceState = rememberUpdatedState(interactionSource)
-    return dragForEachGesture(
-        orientation = orientationState,
-        enabled = enabledState,
-        scrollableState = controllerState,
-        nestedScrollDispatcher = nestedScrollDispatcher,
-        interactionSource = interactionSourceState,
-        dragStartInteraction = draggedInteraction,
-        scrollLogic = scrollLogic
+    val draggableState = remember { ScrollDraggableState(scrollLogic) }
+
+    return draggable(
+        draggableState,
+        orientation = orientation,
+        enabled = enabled,
+        interactionSource = interactionSource,
+        reverseDirection = false,
+        startDragImmediately = controller.isScrollInProgress,
+        onDragStopped = { velocity ->
+            nestedScrollDispatcher.value.coroutineScope.launch {
+                scrollLogic.value.onDragStopped(velocity)
+            }
+        },
+        canDrag = { down -> down.type != PointerType.Mouse }
     ).nestedScroll(nestedScrollConnection, nestedScrollDispatcher.value)
 }
 
-@Suppress("ComposableModifierFactory")
-@Composable
-private fun Modifier.dragForEachGesture(
-    orientation: State<Orientation>,
-    enabled: State<Boolean>,
-    scrollableState: State<ScrollableState>,
-    nestedScrollDispatcher: State<NestedScrollDispatcher>,
-    interactionSource: State<MutableInteractionSource?>,
-    dragStartInteraction: MutableState<DragInteraction.Start?>,
-    scrollLogic: State<ScrollingLogic>
-): Modifier {
-    fun isVertical() = orientation.value == Vertical
-
-    fun Offset.axisValue() = this.run { if (isVertical()) y else x }
-
-    suspend fun PointerInputScope.initialDown(): Pair<PointerInputChange?, Float> {
-        var initialDelta = 0f
-        return awaitPointerEventScope {
-            val down = awaitFirstDown(requireUnconsumed = false)
-            if (!enabled.value || down.type == PointerType.Mouse) {
-                null to initialDelta
-            } else if (scrollableState.value.isScrollInProgress) {
-                // since we start immediately we don't wait for slop and set initial delta to 0
-                initialDelta = 0f
-                down to initialDelta
-            } else {
-                val onSlopPassed = { event: PointerInputChange, overSlop: Float ->
-                    event.consumePositionChange()
-                    initialDelta = overSlop
-                }
-                val result = if (isVertical()) {
-                    awaitVerticalTouchSlopOrCancellation(down.id, onSlopPassed)
-                } else {
-                    awaitHorizontalTouchSlopOrCancellation(down.id, onSlopPassed)
-                }
-                (if (enabled.value) result else null) to initialDelta
-            }
-        }
-    }
-
-    suspend fun PointerInputScope.mainDragCycle(
-        drag: PointerInputChange,
-        initialDelta: Float,
-        velocityTracker: VelocityTracker,
-    ): Boolean {
-        var result = false
-
-        fun ScrollScope.touchDragTick(event: PointerInputChange) {
-            velocityTracker.addPosition(event.uptimeMillis, event.position)
-            val delta = event.positionChange().axisValue()
-            if (enabled.value) {
-                with(scrollLogic.value) {
-                    dispatchScroll(delta, NestedScrollSource.Drag)
-                }
-            }
-            event.consumePositionChange()
-        }
-
-        try {
-            scrollableState.value.scroll(MutatePriority.UserInput) {
-                awaitPointerEventScope {
-                    if (enabled.value) {
-                        with(scrollLogic.value) {
-                            dispatchScroll(initialDelta, NestedScrollSource.Drag)
-                        }
-                    }
-                    velocityTracker.addPosition(drag.uptimeMillis, drag.position)
-                    val dragTick = { event: PointerInputChange ->
-                        if (event.type != PointerType.Mouse) {
-                            touchDragTick(event)
-                        }
-                    }
-                    result = if (isVertical()) {
-                        verticalDrag(drag.id, dragTick)
-                    } else {
-                        horizontalDrag(drag.id, dragTick)
-                    }
-                }
-            }
-        } catch (c: CancellationException) {
-            result = false
-        }
-        return result
-    }
-
-    suspend fun fling(velocity: Velocity) {
-        val preConsumedByParent = nestedScrollDispatcher.value.dispatchPreFling(velocity)
-        val available = velocity - preConsumedByParent
-        val velocityLeft = scrollLogic.value.doFlingAnimation(available)
-        nestedScrollDispatcher.value.dispatchPostFling(available - velocityLeft, velocityLeft)
-    }
-
-    val scrollLambda: suspend PointerInputScope.() -> Unit = remember {
-        {
-            forEachGesture {
-                val (startEvent, initialDelta) = initialDown()
-                if (startEvent != null) {
-                    val velocityTracker = VelocityTracker()
-                    // remember enabled state when we add interaction to remove later if needed
-                    val enabledWhenInteractionAdded = enabled.value
-                    if (enabledWhenInteractionAdded) {
-                        coroutineScope {
-                            launch {
-                                dragStartInteraction.value?.let { oldInteraction ->
-                                    interactionSource.value?.emit(
-                                        DragInteraction.Cancel(oldInteraction)
-                                    )
-                                }
-                                val interaction = DragInteraction.Start()
-                                interactionSource.value?.emit(interaction)
-                                dragStartInteraction.value = interaction
-                            }
-                        }
-                    }
-                    val isDragSuccessful = mainDragCycle(startEvent, initialDelta, velocityTracker)
-                    if (enabledWhenInteractionAdded) {
-                        coroutineScope {
-                            launch {
-                                dragStartInteraction.value?.let { interaction ->
-                                    interactionSource.value?.emit(
-                                        DragInteraction.Stop(interaction)
-                                    )
-                                    dragStartInteraction.value = null
-                                }
-                            }
-                        }
-                    }
-                    if (isDragSuccessful) {
-                        nestedScrollDispatcher.value.coroutineScope.launch {
-                            fling(velocityTracker.calculateVelocity())
-                        }
-                    }
-                }
-            }
-        }
-    }
-    return pointerInput(scrollLambda, scrollLambda)
-}
-
 private class ScrollingLogic(
     val orientation: Orientation,
     val reverseDirection: Boolean,
@@ -362,12 +199,20 @@
         }
     }
 
+    suspend fun onDragStopped(axisVelocity: Float) {
+        val velocity = axisVelocity.toVelocity()
+        val preConsumedByParent = nestedScrollDispatcher.value.dispatchPreFling(velocity)
+        val available = velocity - preConsumedByParent
+        val velocityLeft = doFlingAnimation(available)
+        nestedScrollDispatcher.value.dispatchPostFling(available - velocityLeft, velocityLeft)
+    }
+
     suspend fun doFlingAnimation(available: Velocity): Velocity {
         var result: Velocity = available
         // come up with the better threshold, but we need it since spline curve gives us NaNs
-        if (abs(available.toFloat()) > 1f) scrollableState.scroll {
+        scrollableState.scroll {
             val outerScopeScroll: (Float) -> Float =
-                { delta -> this.dispatchScroll(delta, NestedScrollSource.Fling) }
+                { delta -> this.dispatchScroll(delta.reverseIfNeeded(), NestedScrollSource.Fling) }
             val scope = object : ScrollScope {
                 override fun scrollBy(pixels: Float): Float {
                     return outerScopeScroll.invoke(pixels)
@@ -375,7 +220,7 @@
             }
             with(scope) {
                 with(flingBehavior) {
-                    result = performFling(available.toFloat()).toVelocity()
+                    result = performFling(available.toFloat().reverseIfNeeded()).toVelocity()
                 }
             }
         }
@@ -383,6 +228,38 @@
     }
 }
 
+private class ScrollDraggableState(
+    val scrollLogic: State<ScrollingLogic>
+) : DraggableState, DragScope {
+    var latestScrollScope: ScrollScope = NoOpScrollScope
+
+    override fun dragBy(pixels: Float) {
+        with(scrollLogic.value) {
+            with(latestScrollScope) {
+                dispatchScroll(pixels, NestedScrollSource.Drag)
+            }
+        }
+    }
+
+    override suspend fun drag(
+        dragPriority: MutatePriority,
+        block: suspend DragScope.() -> Unit
+    ) {
+        scrollLogic.value.scrollableState.scroll(dragPriority) {
+            latestScrollScope = this
+            block()
+        }
+    }
+
+    override fun dispatchRawDelta(delta: Float) {
+        with(scrollLogic.value) { performRawScroll(delta.toOffset()) }
+    }
+}
+
+private val NoOpScrollScope: ScrollScope = object : ScrollScope {
+    override fun scrollBy(pixels: Float): Float = pixels
+}
+
 private fun scrollableNestedScrollConnection(
     scrollLogic: State<ScrollingLogic>
 ): NestedScrollConnection = object : NestedScrollConnection {
@@ -405,19 +282,23 @@
     private val flingDecay: DecayAnimationSpec<Float>
 ) : FlingBehavior {
     override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
-        var velocityLeft = initialVelocity
-        var lastValue = 0f
-        AnimationState(
-            initialValue = 0f,
-            initialVelocity = initialVelocity,
-        ).animateDecay(flingDecay) {
-            val delta = value - lastValue
-            val left = scrollBy(delta)
-            lastValue = value
-            velocityLeft = this.velocity
-            // avoid rounding errors and stop if anything is unconsumed
-            if (abs(left) > 0.5f) this.cancelAnimation()
+        return if (abs(initialVelocity) > 1f) {
+            var velocityLeft = initialVelocity
+            var lastValue = 0f
+            AnimationState(
+                initialValue = 0f,
+                initialVelocity = initialVelocity,
+            ).animateDecay(flingDecay) {
+                val delta = value - lastValue
+                val left = scrollBy(delta)
+                lastValue = value
+                velocityLeft = this.velocity
+                // avoid rounding errors and stop if anything is unconsumed
+                if (abs(left) > 0.5f) this.cancelAnimation()
+            }
+            velocityLeft
+        } else {
+            initialVelocity
         }
-        return velocityLeft
     }
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/interaction/DragInteraction.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/interaction/DragInteraction.kt
index 1a0374d..6a3d06c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/interaction/DragInteraction.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/interaction/DragInteraction.kt
@@ -19,8 +19,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import kotlinx.coroutines.flow.collect
 
@@ -79,15 +78,17 @@
  */
 @Composable
 fun InteractionSource.collectIsDraggedAsState(): State<Boolean> {
-    val dragInteractions = remember { mutableStateListOf<DragInteraction.Start>() }
+    val isDragged = remember { mutableStateOf(false) }
     LaunchedEffect(this) {
+        val dragInteractions = mutableListOf<DragInteraction.Start>()
         interactions.collect { interaction ->
             when (interaction) {
                 is DragInteraction.Start -> dragInteractions.add(interaction)
                 is DragInteraction.Stop -> dragInteractions.remove(interaction.start)
                 is DragInteraction.Cancel -> dragInteractions.remove(interaction.start)
             }
+            isDragged.value = dragInteractions.isNotEmpty()
         }
     }
-    return remember { derivedStateOf { dragInteractions.isNotEmpty() } }
+    return isDragged
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/interaction/FocusInteraction.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/interaction/FocusInteraction.kt
index 794d959..010f950 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/interaction/FocusInteraction.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/interaction/FocusInteraction.kt
@@ -19,8 +19,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import kotlinx.coroutines.flow.collect
 
@@ -64,14 +63,16 @@
  */
 @Composable
 fun InteractionSource.collectIsFocusedAsState(): State<Boolean> {
-    val focusInteractions = remember { mutableStateListOf<FocusInteraction.Focus>() }
+    val isFocused = remember { mutableStateOf(false) }
     LaunchedEffect(this) {
+        val focusInteractions = mutableListOf<FocusInteraction.Focus>()
         interactions.collect { interaction ->
             when (interaction) {
                 is FocusInteraction.Focus -> focusInteractions.add(interaction)
                 is FocusInteraction.Unfocus -> focusInteractions.remove(interaction.focus)
             }
+            isFocused.value = focusInteractions.isNotEmpty()
         }
     }
-    return remember { derivedStateOf { focusInteractions.isNotEmpty() } }
+    return isFocused
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/interaction/PressInteraction.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/interaction/PressInteraction.kt
index 5ec419c..5545c8e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/interaction/PressInteraction.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/interaction/PressInteraction.kt
@@ -19,8 +19,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.ui.geometry.Offset
 import kotlinx.coroutines.flow.collect
@@ -80,15 +79,17 @@
  */
 @Composable
 fun InteractionSource.collectIsPressedAsState(): State<Boolean> {
-    val pressInteractions = remember { mutableStateListOf<PressInteraction.Press>() }
+    val isPressed = remember { mutableStateOf(false) }
     LaunchedEffect(this) {
+        val pressInteractions = mutableListOf<PressInteraction.Press>()
         interactions.collect { interaction ->
             when (interaction) {
                 is PressInteraction.Press -> pressInteractions.add(interaction)
                 is PressInteraction.Release -> pressInteractions.remove(interaction.press)
                 is PressInteraction.Cancel -> pressInteractions.remove(interaction.press)
             }
+            isPressed.value = pressInteractions.isNotEmpty()
         }
     }
-    return remember { derivedStateOf { pressInteractions.isNotEmpty() } }
+    return isPressed
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
index 466d2a8..a9d3e6c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
@@ -135,6 +135,7 @@
                 horizontalAlignment = horizontalAlignment,
                 verticalAlignment = verticalAlignment,
                 layoutDirection = layoutDirection,
+                reverseLayout = reverseLayout,
                 startContentPadding = startContentPadding,
                 endContentPadding = endContentPadding,
                 spacing = spacing,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListHeaders.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListHeaders.kt
index 2c4b295..16b5e4c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListHeaders.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListHeaders.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.lazy
 
 import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.util.fastFirstOrNull
 
 /**
  * @param itemProvider the provider so we can compose a header if it wasn't composed already
@@ -71,14 +72,13 @@
         scope: Placeable.PlacementScope,
         layoutWidth: Int,
         layoutHeight: Int,
-        offset: Int,
-        reverseOrder: Boolean
+        offset: Int
     ) {
         if (item.index == currentHeaderListPosition) {
             currentHeaderItem = item
             currentHeaderOffset = offset
         } else {
-            item.place(scope, layoutWidth, layoutHeight, offset, reverseOrder)
+            item.place(scope, layoutWidth, layoutHeight, offset)
             if (item.index == nextHeaderListPosition) {
                 nextHeaderOffset = offset
                 nextHeaderSize = item.size
@@ -88,10 +88,8 @@
 
     fun onAfterItemsPlacing(
         scope: Placeable.PlacementScope,
-        mainAxisLayoutSize: Int,
         layoutWidth: Int,
-        layoutHeight: Int,
-        reverseOrder: Boolean
+        layoutHeight: Int
     ) {
         if (currentHeaderListPosition == -1) {
             // we have no headers needing special handling
@@ -99,35 +97,20 @@
         }
 
         val headerItem = currentHeaderItem
-            ?: notUsedButComposedItems?.find { it.index == currentHeaderListPosition }
+            ?: notUsedButComposedItems?.fastFirstOrNull { it.index == currentHeaderListPosition }
             ?: itemProvider.getAndMeasure(DataIndex(currentHeaderListPosition))
 
-        var headerOffset = if (!reverseOrder) {
-            if (currentHeaderOffset != Int.MIN_VALUE) {
-                maxOf(-startContentPadding, currentHeaderOffset)
-            } else {
-                -startContentPadding
-            }
+        var headerOffset = if (currentHeaderOffset != Int.MIN_VALUE) {
+            maxOf(-startContentPadding, currentHeaderOffset)
         } else {
-            if (currentHeaderOffset != Int.MIN_VALUE) {
-                minOf(
-                    mainAxisLayoutSize + startContentPadding - headerItem.size,
-                    currentHeaderOffset
-                )
-            } else {
-                mainAxisLayoutSize + startContentPadding - headerItem.size
-            }
+            -startContentPadding
         }
         // if we have a next header overlapping with the current header, the next one will be
         // pushing the current one away from the viewport.
         if (nextHeaderOffset != Int.MIN_VALUE) {
-            if (!reverseOrder) {
-                headerOffset = minOf(headerOffset, nextHeaderOffset - headerItem.size)
-            } else {
-                headerOffset = maxOf(headerOffset, nextHeaderOffset + headerItem.size)
-            }
+            headerOffset = minOf(headerOffset, nextHeaderOffset - headerItem.size)
         }
 
-        headerItem.place(scope, layoutWidth, layoutHeight, headerOffset, reverseOrder)
+        headerItem.place(scope, layoutWidth, layoutHeight, headerOffset)
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
index f1f753c..7ee96d0 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
@@ -259,41 +259,42 @@
     return layout(layoutWidth, layoutHeight) {
         var currentMainAxis = measureResult.itemsScrollOffset
         if (hasSpareSpace) {
-            val items = if (reverseLayout) measureResult.items.reversed() else measureResult.items
-            val sizes = IntArray(items.size) { index ->
-                items[index].size
+            val itemsCount = measureResult.items.size
+            val sizes = IntArray(itemsCount) { index ->
+                val reverseLayoutAwareIndex = if (!reverseLayout) index else itemsCount - index - 1
+                measureResult.items[reverseLayoutAwareIndex].size
             }
-            val positions = IntArray(items.size) { 0 }
+            val offsets = IntArray(itemsCount) { 0 }
             if (isVertical) {
                 with(requireNotNull(verticalArrangement)) {
-                    density.arrange(mainAxisLayoutSize, sizes, positions)
+                    density.arrange(mainAxisLayoutSize, sizes, offsets)
                 }
             } else {
                 with(requireNotNull(horizontalArrangement)) {
-                    density.arrange(mainAxisLayoutSize, sizes, layoutDirection, positions)
+                    density.arrange(mainAxisLayoutSize, sizes, layoutDirection, offsets)
                 }
             }
-            positions.forEachIndexed { index, position ->
-                items[index].place(this, layoutWidth, layoutHeight, position, reverseLayout)
+            offsets.forEachIndexed { index, absoluteOffset ->
+                val reverseLayoutAwareIndex = if (!reverseLayout) index else itemsCount - index - 1
+                val item = measureResult.items[reverseLayoutAwareIndex]
+                val relativeOffset = if (reverseLayout) {
+                    mainAxisLayoutSize - absoluteOffset - item.size
+                } else {
+                    absoluteOffset
+                }
+                item.place(this, layoutWidth, layoutHeight, relativeOffset)
             }
         } else {
             headers?.onBeforeItemsPlacing()
             measureResult.items.fastForEach {
-                val offset = if (reverseLayout) {
-                    mainAxisLayoutSize - currentMainAxis - (it.size)
-                } else {
-                    currentMainAxis
-                }
                 if (headers != null) {
-                    headers.place(it, this, layoutWidth, layoutHeight, offset, reverseLayout)
+                    headers.place(it, this, layoutWidth, layoutHeight, currentMainAxis)
                 } else {
-                    it.place(this, layoutWidth, layoutHeight, offset, reverseLayout)
+                    it.place(this, layoutWidth, layoutHeight, currentMainAxis)
                 }
                 currentMainAxis += it.sizeWithSpacings
             }
-            headers?.onAfterItemsPlacing(
-                this, mainAxisLayoutSize, layoutWidth, layoutHeight, reverseLayout
-            )
+            headers?.onAfterItemsPlacing(this, layoutWidth, layoutHeight)
         }
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScrolling.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScrolling.kt
index ea2398a..3219a4e 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScrolling.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListScrolling.kt
@@ -21,6 +21,7 @@
 import androidx.compose.animation.core.animateTo
 import androidx.compose.animation.core.spring
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastFirstOrNull
 import kotlin.coroutines.cancellation.CancellationException
 
 private class ItemFoundInScroll(val item: LazyListItemInfo) : CancellationException()
@@ -33,7 +34,7 @@
     scrollOffset: Int
 ) {
     val animationSpec: AnimationSpec<Float> = spring()
-    fun getTargetItem() = layoutInfo.visibleItemsInfo.firstOrNull {
+    fun getTargetItem() = layoutInfo.visibleItemsInfo.fastFirstOrNull {
         it.index == index
     }
     scroll {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItem.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItem.kt
index 1d084e3..591aef6 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItem.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItem.kt
@@ -31,6 +31,7 @@
     private val horizontalAlignment: Alignment.Horizontal?,
     private val verticalAlignment: Alignment.Vertical?,
     private val layoutDirection: LayoutDirection,
+    private val reverseLayout: Boolean,
     private val startContentPadding: Int,
     private val endContentPadding: Int,
     /**
@@ -81,15 +82,19 @@
         scope: Placeable.PlacementScope,
         layoutWidth: Int,
         layoutHeight: Int,
-        offset: Int,
-        reverseOrder: Boolean
+        offset: Int
     ) = with(scope) {
         [email protected] = offset
-        var mainAxisOffset = offset
-        var index = if (reverseOrder) placeables.lastIndex else 0
-        while (if (reverseOrder) index >= 0 else index < placeables.size) {
+        val mainAxisLayoutSize = if (isVertical) layoutHeight else layoutWidth
+        var mainAxisOffset = if (reverseLayout) {
+            mainAxisLayoutSize - offset - size
+        } else {
+            offset
+        }
+        var index = if (reverseLayout) placeables.lastIndex else 0
+        while (if (reverseLayout) index >= 0 else index < placeables.size) {
             val it = placeables[index]
-            if (reverseOrder) index-- else index++
+            if (reverseLayout) index-- else index++
             if (isVertical) {
                 val x = requireNotNull(horizontalAlignment)
                     .align(it.width, layoutWidth, layoutDirection)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/legacygestures/DragGestureFilter.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/legacygestures/DragGestureFilter.kt
index a9fe256..b9e4ec3 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/legacygestures/DragGestureFilter.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/legacygestures/DragGestureFilter.kt
@@ -18,6 +18,8 @@
 
 package androidx.compose.foundation.legacygestures
 
+import androidx.compose.foundation.fastFilter
+import androidx.compose.foundation.fastFold
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
@@ -42,6 +44,7 @@
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastAll
 import androidx.compose.ui.util.fastAny
 import androidx.compose.ui.util.fastForEach
 import kotlinx.coroutines.CoroutineScope
@@ -305,7 +308,7 @@
                     }
                 }
 
-                if (changes.all { it.changedToUpIgnoreConsumed() }) {
+                if (changes.fastAll { it.changedToUpIgnoreConsumed() }) {
                     // All of the pointers are up, so reset and call onStop.  If we have a
                     // velocityTracker at this point, that means at least one of the up events
                     // was not consumed so we should send velocity for flinging.
@@ -348,7 +351,7 @@
 
             // Handle moved changes.
 
-            val movedChanges = changes.filter {
+            val movedChanges = changes.fastFilter {
                 it.pressed && !it.changedToDownIgnoreConsumed()
             }
 
@@ -540,12 +543,12 @@
         val changes = pointerEvent.changes
 
         if (pass == executionPass) {
-            if (enabled && changes.all { it.changedToDown() }) {
+            if (enabled && changes.fastAll { it.changedToDown() }) {
                 // If we have not yet started and all of the changes changed to down, we are
                 // starting.
                 active = true
                 onPressStart(changes.first().position)
-            } else if (changes.all { it.changedToUp() }) {
+            } else if (changes.fastAll { it.changedToUp() }) {
                 // If we have started and all of the changes changed to up, we are stopping.
                 active = false
             }
@@ -675,7 +678,7 @@
             }
 
             if (pass == PointerEventPass.Final &&
-                changes.all { it.changedToUpIgnoreConsumed() }
+                changes.fastAll { it.changedToUpIgnoreConsumed() }
             ) {
                 // On the final pass, check to see if all pointers have changed to up, and if they
                 // have, reset.
@@ -705,7 +708,7 @@
         return Offset.Zero
     }
 
-    val sum = changes.fold(Offset.Zero) { sum, change ->
+    val sum = changes.fastFold(Offset.Zero) { sum, change ->
         sum + change.positionChange()
     }
     val sizeAsFloat = changes.size.toFloat()
@@ -887,7 +890,7 @@
         if (pass == PointerEventPass.Main &&
             dragEnabled &&
             !dragStarted &&
-            pointerEvent.changes.all { it.changedToUpIgnoreConsumed() }
+            pointerEvent.changes.fastAll { it.changedToUpIgnoreConsumed() }
         ) {
             dragEnabled = false
             longPressDragObserver.onStop(Offset.Zero)
@@ -961,11 +964,11 @@
         }
 
         if (pass == PointerEventPass.Main) {
-            if (state == State.Idle && changes.all { it.changedToDown() }) {
+            if (state == State.Idle && changes.fastAll { it.changedToDown() }) {
                 // If we are idle and all of the changes changed to down, we are prime to fire
                 // the event.
                 primeToFire()
-            } else if (state != State.Idle && changes.all { it.changedToUpIgnoreConsumed() }) {
+            } else if (state != State.Idle && changes.fastAll { it.changedToUpIgnoreConsumed() }) {
                 // If we have started and all of the changes changed to up, reset to idle.
                 resetToIdle()
             } else if (!changes.anyPointersInBounds(bounds)) {
@@ -976,7 +979,7 @@
             if (state == State.Primed) {
                 // If we are primed, keep track of all down pointer positions so we can pass
                 // pointer position information to the event we will fire.
-                changes.forEach {
+                changes.fastForEach {
                     if (it.pressed) {
                         pointerPositions[it.id] = it.position
                     } else {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/legacygestures/PressIndicatorGestureFilter.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/legacygestures/PressIndicatorGestureFilter.kt
index 4a91550..ccf2dbf 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/legacygestures/PressIndicatorGestureFilter.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/legacygestures/PressIndicatorGestureFilter.kt
@@ -31,6 +31,7 @@
 import androidx.compose.ui.input.pointer.consumeDownChange
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.util.fastAll
 import androidx.compose.ui.util.fastAny
 import androidx.compose.ui.util.fastForEach
 
@@ -181,13 +182,13 @@
 
         if (pass == PointerEventPass.Main) {
 
-            if (state == State.Idle && changes.all { it.changedToDown() }) {
+            if (state == State.Idle && changes.fastAll { it.changedToDown() }) {
                 // If we have not yet started and all of the changes changed to down, we are
                 // starting.
                 state = State.Started
                 onStart?.invoke(changes.first().position)
             } else if (state == State.Started) {
-                if (changes.all { it.changedToUpIgnoreConsumed() }) {
+                if (changes.fastAll { it.changedToUpIgnoreConsumed() }) {
                     // If we have started and all of the changes changed to up, we are stopping.
                     state = State.Idle
                     onStop?.invoke()
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/legacygestures/TapGestureFilter.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/legacygestures/TapGestureFilter.kt
index d7d04fc..06e73d2 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/legacygestures/TapGestureFilter.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/legacygestures/TapGestureFilter.kt
@@ -33,6 +33,7 @@
 import androidx.compose.ui.input.pointer.consumeDownChange
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.util.fastAll
 import androidx.compose.ui.util.fastAny
 import androidx.compose.ui.util.fastForEach
 
@@ -109,7 +110,7 @@
         if (pass == PointerEventPass.Main) {
 
             if (primed &&
-                changes.all { it.changedToUp() }
+                changes.fastAll { it.changedToUp() }
             ) {
                 val pointerPxPosition: Offset = changes[0].previousPosition
                 if (changes.fastAny { !upBlockedPointers.contains(it.id) }) {
@@ -128,7 +129,7 @@
                 }
             }
 
-            if (changes.all { it.changedToDown() }) {
+            if (changes.fastAll { it.changedToDown() }) {
                 // Reset in case we were incorrectly left waiting on a delayUp message.
                 reset()
                 // If all of the changes are down, can become primed.
@@ -136,7 +137,7 @@
             }
 
             if (primed) {
-                changes.forEach {
+                changes.fastForEach {
                     if (it.changedToDown()) {
                         downPointers.add(it.id)
                     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/CornerSize.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/CornerSize.kt
index 4f700c8..3e2bb03 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/CornerSize.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/shape/CornerSize.kt
@@ -19,6 +19,7 @@
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 
@@ -45,11 +46,14 @@
 @Stable
 fun CornerSize(size: Dp): CornerSize = DpCornerSize(size)
 
-private data class DpCornerSize(private val size: Dp) : CornerSize {
+private data class DpCornerSize(private val size: Dp) : CornerSize, InspectableValue {
     override fun toPx(shapeSize: Size, density: Density) =
         with(density) { size.toPx() }
 
     override fun toString(): String = "CornerSize(size = ${size.value}.dp)"
+
+    override val valueOverride: Dp
+        get() = size
 }
 
 /**
@@ -59,10 +63,13 @@
 @Stable
 fun CornerSize(size: Float): CornerSize = PxCornerSize(size)
 
-private data class PxCornerSize(private val size: Float) : CornerSize {
+private data class PxCornerSize(private val size: Float) : CornerSize, InspectableValue {
     override fun toPx(shapeSize: Size, density: Density) = size
 
     override fun toString(): String = "CornerSize(size = $size.px)"
+
+    override val valueOverride: String
+        get() = "${size}px"
 }
 
 /**
@@ -82,7 +89,7 @@
 private data class PercentCornerSize(
     /*@FloatRange(from = 0.0, to = 100.0)*/
     private val percent: Float
-) : CornerSize {
+) : CornerSize, InspectableValue {
     init {
         if (percent < 0 || percent > 100) {
             throw IllegalArgumentException("The percent should be in the range of [0, 100]")
@@ -93,14 +100,20 @@
         shapeSize.minDimension * (percent / 100f)
 
     override fun toString(): String = "CornerSize(size = $percent%)"
+
+    override val valueOverride: String
+        get() = "$percent%"
 }
 
 /**
  * [CornerSize] always equals to zero.
  */
 @Stable
-val ZeroCornerSize: CornerSize = object : CornerSize {
+val ZeroCornerSize: CornerSize = object : CornerSize, InspectableValue {
     override fun toPx(shapeSize: Size, density: Density) = 0.0f
 
     override fun toString(): String = "ZeroCornerSize"
+
+    override val valueOverride: String
+        get() = "ZeroCornerSize"
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
index 7f72f56..bc38313 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
@@ -17,17 +17,14 @@
 
 package androidx.compose.foundation.text
 
+import androidx.compose.foundation.fastMapIndexedNotNull
 import androidx.compose.foundation.legacygestures.DragObserver
 import androidx.compose.foundation.text.selection.MultiWidgetSelectionDelegate
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.DisposableEffectResult
 import androidx.compose.runtime.DisposableEffectScope
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.structuralEqualityPolicy
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.geometry.Offset
@@ -54,12 +51,14 @@
 import androidx.compose.foundation.text.selection.LocalTextSelectionColors
 import androidx.compose.foundation.text.selection.Selectable
 import androidx.compose.foundation.text.selection.SelectionRegistrar
+import androidx.compose.foundation.text.selection.hasSelection
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.ui.semantics.getTextLayoutResult
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.Placeholder
 import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.Font
 import androidx.compose.ui.text.style.TextAlign
@@ -68,6 +67,7 @@
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastMap
 import kotlin.math.floor
 import kotlin.math.roundToInt
 
@@ -117,6 +117,16 @@
 
     val (placeholders, inlineComposables) = resolveInlineContent(text, inlineContent)
 
+    // The ID used to identify this CoreText. If this CoreText is removed from the composition
+    // tree and then added back, this ID should stay the same.
+    // Notice that we need to update selectable ID when the input text or selectionRegistrar has
+    // been updated.
+    // When text is updated, the selection on this CoreText becomes invalid. It can be treated
+    // as a brand new CoreText.
+    // When SelectionRegistrar is updated, CoreText have to request a new ID to avoid ID collision.
+    val selectableId = rememberSaveable(text, selectionRegistrar) {
+        selectionRegistrar?.nextSelectableId() ?: SelectionRegistrar.InvalidSelectableId
+    }
     val state = remember {
         TextState(
             TextDelegate(
@@ -128,7 +138,8 @@
                 overflow = overflow,
                 maxLines = maxLines,
                 placeholders = placeholders
-            )
+            ),
+            selectableId
         )
     }
     state.textDelegate = updateTextDelegate(
@@ -193,7 +204,7 @@
         Layout(
             content = { content(text.subSequence(start, end).text) }
         ) { children, constrains ->
-            val placeables = children.map { it.measure(constrains) }
+            val placeables = children.fastMap { it.measure(constrains) }
             layout(width = constrains.maxWidth, height = constrains.maxHeight) {
                 placeables.fastForEach { it.placeRelative(0, 0) }
             }
@@ -209,33 +220,16 @@
         this.selectionRegistrar = selectionRegistrar
     }
 
-    val modifiers = Modifier.graphicsLayer().drawBehind {
-        state.layoutResult?.let { layoutResult ->
-            drawIntoCanvas { canvas ->
-                state.selectionRange?.let {
-                    TextDelegate.paintBackground(
-                        it.min,
-                        it.max,
-                        state.selectionPaint,
-                        canvas,
-                        layoutResult
-                    )
-                }
-                TextDelegate.paint(canvas, layoutResult)
-            }
-        }
-    }.onGloballyPositioned {
+    val modifiers = Modifier.drawTextAndSelectionBehind().onGloballyPositioned {
         // Get the layout coordinates of the text composable. This is for hit test of
         // cross-composable selection.
         state.layoutCoordinates = it
-        selectionRegistrar?.let { selectionRegistrar ->
-            if (state.selectionRange != null) {
-                val newGlobalPosition = it.positionInWindow()
-                if (newGlobalPosition != state.previousGlobalPosition) {
-                    selectionRegistrar.notifyPositionChange()
-                }
-                state.previousGlobalPosition = newGlobalPosition
+        if (selectionRegistrar.hasSelection(state.selectableId)) {
+            val newGlobalPosition = it.positionInWindow()
+            if (newGlobalPosition != state.previousGlobalPosition) {
+                selectionRegistrar?.notifyPositionChange()
             }
+            state.previousGlobalPosition = newGlobalPosition
         }
     }.semantics {
         getTextLayoutResult {
@@ -264,16 +258,14 @@
                 state.layoutResult?.let { prevLayoutResult ->
                     // If the input text of this CoreText has changed, notify the SelectionContainer.
                     if (prevLayoutResult.layoutInput.text != layoutResult.layoutInput.text) {
-                        state.selectable?.let { selectable ->
-                            selectionRegistrar?.notifySelectableChange(selectable)
-                        }
+                        selectionRegistrar?.notifySelectableChange(state.selectableId)
                     }
                 }
             }
             state.layoutResult = layoutResult
 
             check(measurables.size >= layoutResult.placeholderRects.size)
-            val placeables = layoutResult.placeholderRects.mapIndexedNotNull { index, rect ->
+            val placeables = layoutResult.placeholderRects.fastMapIndexedNotNull { index, rect ->
                 // PlaceholderRect will be null if it's ellipsized. In that case, the corresponding
                 // inline children won't be measured or placed.
                 rect?.let {
@@ -350,38 +342,68 @@
 
     val commit: DisposableEffectScope.() -> DisposableEffectResult = {
         // if no SelectionContainer is added as parent selectionRegistrar will be null
-        state.selectable = selectionRegistrar?.let { selectionRegistrar ->
-            selectionRegistrar.subscribe(
-                MultiWidgetSelectionDelegate(
-                    selectionRangeUpdate = { state.selectionRange = it },
-                    coordinatesCallback = { state.layoutCoordinates },
-                    layoutResultCallback = { state.layoutResult }
-                )
+        selectionRegistrar?.let { selectionRegistrar ->
+            val selectable = MultiWidgetSelectionDelegate(
+                state.selectableId,
+                coordinatesCallback = { state.layoutCoordinates },
+                layoutResultCallback = { state.layoutResult }
             )
+            selectionRegistrar.subscribe(selectable)
         }
-
         onDispose {
-            // unregister only if any id was provided by SelectionRegistrar
             state.selectable?.let { selectionRegistrar?.unsubscribe(it) }
         }
     }
+
+    /**
+     * Draw the given selection on the canvas.
+     */
+    @Stable
+    @OptIn(InternalFoundationTextApi::class)
+    private fun Modifier.drawTextAndSelectionBehind(): Modifier =
+        this.graphicsLayer().drawBehind {
+            drawIntoCanvas { canvas ->
+                val textLayoutResult = state.layoutResult
+                val selectionPaint = state.selectionPaint
+                val selection = selectionRegistrar?.subselections?.get(state.selectableId)
+
+                if (textLayoutResult == null) return@drawIntoCanvas
+                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
+                    }
+                    TextDelegate.paintBackground(
+                        start,
+                        end,
+                        selectionPaint,
+                        canvas,
+                        textLayoutResult
+                    )
+                }
+                TextDelegate.paint(canvas, textLayoutResult)
+            }
+        }
 }
 
 @OptIn(InternalFoundationTextApi::class)
 /*@VisibleForTesting*/
 internal class TextState(
-    var textDelegate: TextDelegate
+    var textDelegate: TextDelegate,
+    /** The selectable Id assigned to the [selectable] */
+    val selectableId: Long
 ) {
     var onTextLayout: (TextLayoutResult) -> Unit = {}
 
     /** The [Selectable] associated with this [CoreText]. */
     var selectable: Selectable? = null
-    /**
-     * The current selection range, used by selection.
-     * This should be a state as every time we update the value during the selection we
-     * need to redraw it. state observation during onDraw callback will make it work.
-     */
-    var selectionRange by mutableStateOf<TextRange?>(null, structuralEqualityPolicy())
+
     /** The last layout coordinates for the Text's layout, used by selection */
     var layoutCoordinates: LayoutCoordinates? = null
     /** The latest TextLayoutResult calculated in the measure block */
@@ -499,7 +521,7 @@
 
         override fun onDragStart() {
             // selection never started
-            if (state.selectionRange == null) return
+            if (!selectionRegistrar.hasSelection(state.selectableId)) return
             // Zero out the total distance that being dragged.
             dragTotalDistance = Offset.Zero
         }
@@ -508,7 +530,7 @@
             state.layoutCoordinates?.let {
                 if (!it.isAttached) return Offset.Zero
                 // selection never started, did not consume any drag
-                if (state.selectionRange == null) return Offset.Zero
+                if (!selectionRegistrar.hasSelection(state.selectableId)) return Offset.Zero
 
                 dragTotalDistance += dragDistance
 
@@ -522,11 +544,15 @@
         }
 
         override fun onStop(velocity: Offset) {
-            selectionRegistrar?.notifySelectionUpdateEnd()
+            if (selectionRegistrar.hasSelection(state.selectableId)) {
+                selectionRegistrar?.notifySelectionUpdateEnd()
+            }
         }
 
         override fun onCancel() {
-            selectionRegistrar?.notifySelectionUpdateEnd()
+            if (selectionRegistrar.hasSelection(state.selectableId)) {
+                selectionRegistrar?.notifySelectionUpdateEnd()
+            }
         }
     }
 }
@@ -552,14 +578,14 @@
                 dragBeginPosition = downPosition
             }
 
-            if (state.selectionRange == null) return
+            if (!selectionRegistrar.hasSelection(state.selectableId)) return
             dragTotalDistance = Offset.Zero
         }
 
         override fun onDrag(dragDistance: Offset): Offset {
             state.layoutCoordinates?.let {
                 if (!it.isAttached) return Offset.Zero
-                if (state.selectionRange == null) return Offset.Zero
+                if (!selectionRegistrar.hasSelection(state.selectableId)) return Offset.Zero
 
                 dragTotalDistance += dragDistance
 
@@ -572,4 +598,4 @@
             return dragDistance
         }
     }
-}
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegate.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegate.kt
index 98e3743..81d495a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegate.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegate.kt
@@ -21,14 +21,14 @@
 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(
-    private val selectionRangeUpdate: (TextRange?) -> Unit,
+    override val selectableId: Long,
     private val coordinatesCallback: () -> LayoutCoordinates?,
     private val layoutResultCallback: () -> TextLayoutResult?
 ) : Selectable {
+
     override fun getSelection(
         startPosition: Offset,
         endPosition: Offset,
@@ -46,7 +46,7 @@
         val startPx = startPosition - relativePosition
         val endPx = endPosition - relativePosition
 
-        val selection = getTextSelectionInfo(
+        return getTextSelectionInfo(
             textLayoutResult = textLayoutResult,
             selectionCoordinates = Pair(startPx, endPx),
             selectable = this,
@@ -54,20 +54,12 @@
             previousSelection = previousSelection,
             isStartHandle = isStartHandle
         )
-
-        return if (selection == null) {
-            selectionRangeUpdate(null)
-            null
-        } else {
-            selectionRangeUpdate(selection.toTextRange())
-            return selection
-        }
     }
 
     override fun getHandlePosition(selection: Selection, isStartHandle: Boolean): Offset {
         // Check if the selection handles's selectable is the current selectable.
-        if (isStartHandle && selection.start.selectable != this ||
-            !isStartHandle && selection.end.selectable != this
+        if (isStartHandle && selection.start.selectableId != this.selectableId ||
+            !isStartHandle && selection.end.selectableId != this.selectableId
         ) {
             return Offset.Zero
         }
@@ -263,7 +255,7 @@
         startOffset = startOffset,
         endOffset = endOffset,
         handlesCrossed = handlesCrossed,
-        selectable = selectable,
+        selectableId = selectable.selectableId,
         textLayoutResult = textLayoutResult
     )
 }
@@ -275,7 +267,8 @@
  * @param startOffset the final start offset to be returned.
  * @param endOffset the final end offset to be returned.
  * @param handlesCrossed true if the selection handles are crossed
- * @param selectable current [Selectable] for which the [Selection] is being calculated
+ * @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.
@@ -284,19 +277,19 @@
     startOffset: Int,
     endOffset: Int,
     handlesCrossed: Boolean,
-    selectable: Selectable,
+    selectableId: Long,
     textLayoutResult: TextLayoutResult
 ): Selection {
     return Selection(
         start = Selection.AnchorInfo(
             direction = textLayoutResult.getBidiRunDirection(startOffset),
             offset = startOffset,
-            selectable = selectable
+            selectableId = selectableId
         ),
         end = Selection.AnchorInfo(
             direction = textLayoutResult.getBidiRunDirection(max(endOffset - 1, 0)),
             offset = endOffset,
-            selectable = selectable
+            selectableId = selectableId
         ),
         handlesCrossed = handlesCrossed
     )
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/Selectable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/Selectable.kt
index b728f69..b3dc106 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/Selectable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/Selectable.kt
@@ -28,6 +28,15 @@
 
 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
+
+    /**
      * Returns [Selection] information for a selectable composable. If no selection can be provided
      * null should be returned.
      *
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/Selection.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/Selection.kt
index b1554f4..bc22a7f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/Selection.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/Selection.kt
@@ -62,9 +62,9 @@
         val offset: Int,
 
         /**
-         * The [Selectable] which contains this [Selection] Anchor.
+         * The id of the [Selectable] which contains this [Selection] Anchor.
          */
-        val selectable: Selectable
+        val selectableId: Long
     )
 
     fun merge(other: Selection?): Selection {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionContainer.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionContainer.kt
index d6f5b95..413118a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionContainer.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionContainer.kt
@@ -30,6 +30,7 @@
 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 it's direct or indirection children.
@@ -98,7 +99,7 @@
             children()
             if (isInTouchMode && manager.hasFocus) {
                 manager.selection?.let {
-                    for (isStartHandle in listOf(true, false)) {
+                    listOf(true, false).fastForEach { isStartHandle ->
                         SelectionHandle(
                             startHandlePosition = manager.startHandlePosition,
                             endHandlePosition = manager.endHandlePosition,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
index 6448b20..53acc0b 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
@@ -18,6 +18,7 @@
 
 package androidx.compose.foundation.text.selection
 
+import androidx.compose.foundation.fastFold
 import androidx.compose.foundation.focusable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
@@ -199,21 +200,38 @@
             showSelectionToolbar()
         }
 
-        selectionRegistrar.onSelectableChangeCallback = { selectable ->
-            if (selectable in selectionRegistrar.selectables) {
+        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
+            }
+        }
     }
 
     private fun updateHandleOffsets() {
         val selection = selection
         val containerCoordinates = containerLayoutCoordinates
-        val startLayoutCoordinates = selection?.start?.selectable?.getLayoutCoordinates()
-        val endLayoutCoordinates = selection?.end?.selectable?.getLayoutCoordinates()
-
+        val startSelectable = selection?.start?.selectableId?.let {
+            selectionRegistrar.selectableMap[it]
+        }
+        val endSelectable = selection?.end?.selectableId?.let {
+            selectionRegistrar.selectableMap[it]
+        }
+        val startLayoutCoordinates = startSelectable?.getLayoutCoordinates()
+        val endLayoutCoordinates = endSelectable?.getLayoutCoordinates()
         if (
             selection == null ||
             containerCoordinates == null ||
@@ -228,14 +246,14 @@
 
         val startHandlePosition = containerCoordinates.localPositionOf(
             startLayoutCoordinates,
-            selection.start.selectable.getHandlePosition(
+            startSelectable.getHandlePosition(
                 selection = selection,
                 isStartHandle = true
             )
         )
         val endHandlePosition = containerCoordinates.localPositionOf(
             endLayoutCoordinates,
-            selection.end.selectable.getHandlePosition(
+            endSelectable.getHandlePosition(
                 selection = selection,
                 isStartHandle = false
             )
@@ -267,8 +285,9 @@
      * @param longPress the selection is a result of long press
      * @param previousSelection previous selection
      *
-     * @return [Selection] object which is constructed by combining all Composables that are
-     * selected.
+     * @return a [Pair] of a [Selection] object which is constructed by combining all
+     * composables that are selected and a [Map] from selectable key to [Selection]s on the
+     * [Selectable] corresponding to the that key.
      */
     // This function is internal for testing purposes.
     internal fun mergeSelections(
@@ -277,26 +296,25 @@
         longPress: Boolean = false,
         previousSelection: Selection? = null,
         isStartHandle: Boolean = true
-    ): Selection? {
-
+    ): Pair<Selection?, Map<Long, Selection>> {
+        val subselections = mutableMapOf<Long, Selection>()
         val newSelection = selectionRegistrar.sort(requireContainerCoordinates())
-            .fold(null) { mergedSelection: Selection?, handler: Selectable ->
-                merge(
-                    mergedSelection,
-                    handler.getSelection(
-                        startPosition = startPosition,
-                        endPosition = endPosition,
-                        containerLayoutCoordinates = requireContainerCoordinates(),
-                        longPress = longPress,
-                        previousSelection = previousSelection,
-                        isStartHandle = isStartHandle
-                    )
+            .fastFold(null) { mergedSelection: Selection?, selectable: Selectable ->
+                val selection = selectable.getSelection(
+                    startPosition = startPosition,
+                    endPosition = endPosition,
+                    containerLayoutCoordinates = requireContainerCoordinates(),
+                    longPress = longPress,
+                    previousSelection = previousSelection,
+                    isStartHandle = isStartHandle
                 )
+                selection?.let { subselections[selectable.selectableId] = it }
+                merge(mergedSelection, selection)
             }
         if (previousSelection != newSelection) hapticFeedBack?.performHapticFeedback(
             HapticFeedbackType.TextHandleMove
         )
-        return newSelection
+        return Pair(newSelection, subselections)
     }
 
     internal fun getSelectedText(): AnnotatedString? {
@@ -304,21 +322,23 @@
         var selectedText: AnnotatedString? = null
 
         selection?.let {
-            for (handler in selectables) {
+            for (i in selectables.indices) {
+                val selectable = selectables[i]
                 // Continue if the current selectable is before the selection starts.
-                if (handler != it.start.selectable && handler != it.end.selectable &&
+                if (selectable.selectableId != it.start.selectableId &&
+                    selectable.selectableId != it.end.selectableId &&
                     selectedText == null
                 ) continue
 
                 val currentSelectedText = getCurrentSelectedText(
-                    selectable = handler,
+                    selectable = selectable,
                     selection = it
                 )
                 selectedText = selectedText?.plus(currentSelectedText) ?: currentSelectedText
 
                 // Break if the current selectable is the last selected selectable.
-                if (handler == it.end.selectable && !it.handlesCrossed ||
-                    handler == it.start.selectable && it.handlesCrossed
+                if (selectable.selectableId == it.end.selectableId && !it.handlesCrossed ||
+                    selectable.selectableId == it.start.selectableId && it.handlesCrossed
                 ) break
             }
         }
@@ -368,23 +388,23 @@
      */
     private fun getContentRect(): Rect {
         val selection = selection ?: return Rect.Zero
-        val startLayoutCoordinates =
-            selection.start.selectable.getLayoutCoordinates() ?: return Rect.Zero
-        val endLayoutCoordinates =
-            selection.end.selectable.getLayoutCoordinates() ?: return Rect.Zero
+        val startSelectable = selectionRegistrar.selectableMap[selection.start.selectableId]
+        val endSelectable = selectionRegistrar.selectableMap[selection.start.selectableId]
+        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,
-                selection.start.selectable.getHandlePosition(
+                startSelectable.getHandlePosition(
                     selection = selection,
                     isStartHandle = true
                 )
             )
             var endOffset = localLayoutCoordinates.localPositionOf(
                 endLayoutCoordinates,
-                selection.end.selectable.getHandlePosition(
+                endSelectable.getHandlePosition(
                     selection = selection,
                     isStartHandle = false
                 )
@@ -400,7 +420,7 @@
                 startLayoutCoordinates,
                 Offset(
                     0f,
-                    selection.start.selectable.getBoundingBox(selection.start.offset).top
+                    startSelectable.getBoundingBox(selection.start.offset).top
                 )
             )
 
@@ -408,7 +428,7 @@
                 endLayoutCoordinates,
                 Offset(
                     0.0f,
-                    selection.end.selectable.getBoundingBox(selection.end.offset).top
+                    endSelectable.getBoundingBox(selection.end.offset).top
                 )
             )
 
@@ -430,17 +450,12 @@
 
     // This is for PressGestureDetector to cancel the selection.
     fun onRelease() {
-        if (containerLayoutCoordinates?.isAttached == true) {
-            // Call mergeSelections with an out of boundary input to inform all text widgets to
-            // cancel their individual selection.
-            mergeSelections(
-                startPosition = Offset(-1f, -1f),
-                endPosition = Offset(-1f, -1f),
-                previousSelection = selection
-            )
-        }
+        selectionRegistrar.subselections = emptyMap()
         hideSelectionToolbar()
-        if (selection != null) onSelectionChange(null)
+        if (selection != null) {
+            onSelectionChange(null)
+            hapticFeedBack?.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+        }
     }
 
     fun handleDragObserver(isStartHandle: Boolean): DragObserver {
@@ -448,24 +463,28 @@
             override fun onStart(downPosition: 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) {
-                    selection.start.selectable.getLayoutCoordinates()!!
+                    startSelectable?.getLayoutCoordinates()!!
                 } else {
-                    selection.end.selectable.getLayoutCoordinates()!!
+                    endSelectable?.getLayoutCoordinates()!!
                 }
 
                 // The position of the character where the drag gesture should begin. This is in
                 // the composable coordinates.
                 val beginCoordinates = getAdjustedCoordinates(
                     if (isStartHandle) {
-                        selection.start.selectable.getHandlePosition(
+                        startSelectable!!.getHandlePosition(
                             selection = selection, isStartHandle = true
                         )
                     } else {
-                        selection.end.selectable.getHandlePosition(
+                        endSelectable!!.getHandlePosition(
                             selection = selection, isStartHandle = false
                         )
                     }
@@ -485,14 +504,17 @@
             override fun onDrag(dragDistance: Offset): Offset {
                 val selection = selection!!
                 dragTotalDistance += dragDistance
-
+                val startSelectable =
+                    selectionRegistrar.selectableMap[selection.start.selectableId]
+                val endSelectable =
+                    selectionRegistrar.selectableMap[selection.end.selectableId]
                 val currentStart = if (isStartHandle) {
                     dragBeginPosition + dragTotalDistance
                 } else {
                     requireContainerCoordinates().localPositionOf(
-                        selection.start.selectable.getLayoutCoordinates()!!,
+                        startSelectable?.getLayoutCoordinates()!!,
                         getAdjustedCoordinates(
-                            selection.start.selectable.getHandlePosition(
+                            startSelectable.getHandlePosition(
                                 selection = selection,
                                 isStartHandle = true
                             )
@@ -502,9 +524,9 @@
 
                 val currentEnd = if (isStartHandle) {
                     requireContainerCoordinates().localPositionOf(
-                        selection.end.selectable.getLayoutCoordinates()!!,
+                        endSelectable?.getLayoutCoordinates()!!,
                         getAdjustedCoordinates(
-                            selection.end.selectable.getHandlePosition(
+                            endSelectable.getHandlePosition(
                                 selection = selection,
                                 isStartHandle = false
                             )
@@ -547,14 +569,17 @@
         isStartHandle: Boolean = true
     ) {
         if (startPosition == null || endPosition == null) return
-        val newSelection = mergeSelections(
+        val (newSelection, newSubselection) = mergeSelections(
             startPosition = startPosition,
             endPosition = endPosition,
             longPress = longPress,
             isStartHandle = isStartHandle,
             previousSelection = selection
         )
-        if (newSelection != selection) onSelectionChange(newSelection)
+        if (newSelection != selection) {
+            selectionRegistrar.subselections = newSubselection
+            onSelectionChange(newSelection)
+        }
     }
 }
 
@@ -571,15 +596,15 @@
     val currentText = selectable.getText()
 
     return if (
-        selectable != selection.start.selectable &&
-        selectable != selection.end.selectable
+        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 == selection.start.selectable &&
-        selectable == selection.end.selectable
+        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.
@@ -588,7 +613,7 @@
         } else {
             currentText.subSequence(selection.start.offset, selection.end.offset)
         }
-    } else if (selectable == selection.start.selectable) {
+    } else if (selectable.selectableId == selection.start.selectableId) {
         // Select partial text content if the current selectable is the start
         // selectable.
         if (selection.handlesCrossed) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrar.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrar.kt
index a8a04c1..5d6fdab 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrar.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrar.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.text.selection
 
+import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.compositionLocalOf
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.layout.LayoutCoordinates
@@ -25,16 +26,32 @@
  */
 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.
      */
@@ -95,9 +112,23 @@
      * Call this method to notify the [SelectionContainer] that the content of the passed
      * selectable has been changed.
      *
-     * @param selectable the selectable whose the content has been updated.
+     * @param selectableId the ID of the selectable whose the content has been updated.
      */
-    fun notifySelectableChange(selectable: Selectable)
+    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
 }
 
 /**
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImpl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImpl.kt
index b924cda..3cda909 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImpl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImpl.kt
@@ -16,6 +16,10 @@
 
 package androidx.compose.foundation.text.selection
 
+import androidx.compose.foundation.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
 
@@ -32,11 +36,26 @@
     private val _selectables = mutableListOf<Selectable>()
 
     /**
-     * Getter for handlers that returns an List.
+     * 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.
      */
@@ -60,16 +79,41 @@
     /**
      * The callback to be invoked when one of the selectable has changed.
      */
-    internal var onSelectableChangeCallback: ((Selectable) -> Unit)? = null
+    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
     }
 
     /**
@@ -80,28 +124,27 @@
         if (!sorted) {
             // Sort selectables by y-coordinate first, and then x-coordinate, to match English
             // hand-writing habit.
-            _selectables.sortWith(
-                Comparator { a: Selectable, b: Selectable ->
-                    val layoutCoordinatesA = a.getLayoutCoordinates()
-                    val layoutCoordinatesB = b.getLayoutCoordinates()
+            _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)
+                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
@@ -133,7 +176,7 @@
         onSelectionUpdateEndCallback?.invoke()
     }
 
-    override fun notifySelectableChange(selectable: Selectable) {
-        onSelectableChangeCallback?.invoke(selectable)
+    override fun notifySelectableChange(selectableId: Long) {
+        onSelectableChangeCallback?.invoke(selectableId)
     }
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SimpleLayout.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SimpleLayout.kt
index db75c9f..5d19876 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SimpleLayout.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SimpleLayout.kt
@@ -16,9 +16,12 @@
 
 package androidx.compose.foundation.text.selection
 
+import androidx.compose.foundation.fastFold
 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.math.max
 
 /**
@@ -28,20 +31,20 @@
 @Composable
 internal fun SimpleLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
     Layout(modifier = modifier, content = content) { measurables, constraints ->
-        val placeables = measurables.map { measurable ->
+        val placeables = measurables.fastMap { measurable ->
             measurable.measure(constraints)
         }
 
-        val width = placeables.fold(0) { maxWidth, placeable ->
+        val width = placeables.fastFold(0) { maxWidth, placeable ->
             max(maxWidth, (placeable.width))
         }
 
-        val height = placeables.fold(0) { minWidth, placeable ->
+        val height = placeables.fastFold(0) { minWidth, placeable ->
             max(minWidth, (placeable.height))
         }
 
         layout(width, height) {
-            placeables.forEach { placeable ->
+            placeables.fastForEach { placeable ->
                 placeable.place(0, 0)
             }
         }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextSelectionDelegate.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextSelectionDelegate.kt
index 9f2da8b..bfffeb1 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextSelectionDelegate.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextSelectionDelegate.kt
@@ -52,12 +52,12 @@
     var endOffset = rawEndOffset
     if (startOffset == endOffset) {
 
+        if (previousSelection == null) return Triple(rawStartOffset, rawStartOffset, false)
         // If the start and end offset are at the same character, and it's not the initial
         // selection, then bound to at least one character.
         val textRange = ensureAtLeastOneChar(
             offset = rawStartOffset,
             lastOffset = lastOffset,
-            previousSelection = previousSelection,
             isStartHandle = isStartHandle,
             handlesCrossed = handlesCrossed
         )
@@ -168,7 +168,6 @@
  * @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 previousSelection previous selected text range.
  * @param isStartHandle true if the start handle is being dragged
  * @param handlesCrossed true if the selection handles are crossed
  *
@@ -177,13 +176,12 @@
 private fun ensureAtLeastOneChar(
     offset: Int,
     lastOffset: Int,
-    previousSelection: TextRange?,
     isStartHandle: Boolean,
     handlesCrossed: 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 || previousSelection == null) return TextRange(offset, offset)
+    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.
diff --git a/compose/foundation/foundation/src/jvmMain/kotlin/androidx/compose/foundation/ActualJvm.kt b/compose/foundation/foundation/src/jvmMain/kotlin/androidx/compose/foundation/ActualJvm.kt
index 7e01354..ae5c0c1 100644
--- a/compose/foundation/foundation/src/jvmMain/kotlin/androidx/compose/foundation/ActualJvm.kt
+++ b/compose/foundation/foundation/src/jvmMain/kotlin/androidx/compose/foundation/ActualJvm.kt
@@ -18,4 +18,6 @@
 
 package androidx.compose.foundation
 
-internal actual typealias AtomicReference<V> = java.util.concurrent.atomic.AtomicReference<V>
\ No newline at end of file
+internal actual typealias AtomicReference<V> = java.util.concurrent.atomic.AtomicReference<V>
+
+internal actual typealias AtomicLong = java.util.concurrent.atomic.AtomicLong
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextSelectionLongPressDragTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextSelectionLongPressDragTest.kt
index 22831e6..8fee380 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextSelectionLongPressDragTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextSelectionLongPressDragTest.kt
@@ -23,13 +23,15 @@
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.foundation.text.selection.Selectable
 import androidx.compose.foundation.text.selection.Selection
-import androidx.compose.foundation.text.selection.SelectionRegistrar
+import androidx.compose.foundation.text.selection.SelectionRegistrarImpl
 import androidx.compose.ui.text.style.ResolvedTextDirection
 import com.google.common.truth.Truth.assertThat
 import com.nhaarman.mockitokotlin2.doReturn
 import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.spy
 import com.nhaarman.mockitokotlin2.times
 import com.nhaarman.mockitokotlin2.verify
+import com.nhaarman.mockitokotlin2.whenever
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -38,35 +40,22 @@
 @RunWith(JUnit4::class)
 @OptIn(InternalFoundationTextApi::class)
 class TextSelectionLongPressDragTest {
-    private val selectionRegistrar = mock<SelectionRegistrar>()
-    private val selectable = mock<Selectable>()
+    private val selectionRegistrar = spy(SelectionRegistrarImpl())
+    private val selectableId = 1L
+    private val selectable = mock<Selectable>().also {
+        whenever(it.selectableId).thenReturn(selectableId)
+    }
 
-    private val startSelectable = mock<Selectable>()
-    private val endSelectable = mock<Selectable>()
-
-    private val fakeInitialSelection: Selection = Selection(
+    private val fakeSelection: Selection = Selection(
         start = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = 0,
-            selectable = startSelectable
+            selectableId = selectableId
         ),
         end = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = 5,
-            selectable = endSelectable
-        )
-    )
-
-    private val fakeResultSelection: Selection = Selection(
-        start = Selection.AnchorInfo(
-            direction = ResolvedTextDirection.Ltr,
-            offset = 5,
-            selectable = endSelectable
-        ),
-        end = Selection.AnchorInfo(
-            direction = ResolvedTextDirection.Ltr,
-            offset = 0,
-            selectable = startSelectable
+            selectableId = selectableId
         )
     )
 
@@ -82,8 +71,7 @@
             on { isAttached } doReturn true
         }
 
-        state = TextState(mock<TextDelegate>())
-        state.selectionRange = null
+        state = TextState(mock(), selectableId)
         state.layoutCoordinates = layoutCoordinates
 
         gesture = longPressDragObserver(
@@ -118,7 +106,7 @@
 //        selectionManager.onRelease()
         // Start the new selection
         gesture.onLongPress(beginPosition2)
-        state.selectionRange = fakeInitialSelection.toTextRange()
+        selectionRegistrar.subselections = mapOf(selectableId to fakeSelection)
 
         // Act. Reset selectionManager.dragTotalDistance to zero.
         gesture.onDragStart()
@@ -138,7 +126,7 @@
         val dragDistance = Offset(15f, 10f)
         val beginPosition = Offset(30f, 20f)
         gesture.onLongPress(beginPosition)
-        state.selectionRange = fakeInitialSelection.toTextRange()
+        selectionRegistrar.subselections = mapOf(selectableId to fakeSelection)
         gesture.onDragStart()
 
         val result = gesture.onDrag(dragDistance)
@@ -157,7 +145,7 @@
         val dragDistance = Offset(15f, 10f)
         val beginPosition = Offset(30f, 20f)
         gesture.onLongPress(beginPosition)
-        state.selectionRange = fakeInitialSelection.toTextRange()
+        selectionRegistrar.subselections = mapOf(selectableId to fakeSelection)
         gesture.onDragStart()
         gesture.onStop(dragDistance)
 
@@ -169,7 +157,7 @@
     fun longPressDragObserver_onCancel_calls_notifySelectionEnd() {
         val beginPosition = Offset(30f, 20f)
         gesture.onLongPress(beginPosition)
-        state.selectionRange = fakeInitialSelection.toTextRange()
+        selectionRegistrar.subselections = mapOf(selectableId to fakeSelection)
         gesture.onDragStart()
         gesture.onCancel()
 
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/MockSelectable.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/MockSelectable.kt
index 196700c..15993e5 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/MockSelectable.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/MockSelectable.kt
@@ -28,6 +28,7 @@
     var getTextValue: AnnotatedString = AnnotatedString(""),
     var getBoundingBoxValue: Rect = Rect.Zero
 ) : Selectable {
+    override var selectableId = 0L
     val getSelectionValues = mutableListOf<GetSelectionParameters>()
     override fun getSelection(
         startPosition: Offset,
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionManagerDragTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionManagerDragTest.kt
index c435e0d..e89b190 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionManagerDragTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionManagerDragTest.kt
@@ -38,7 +38,8 @@
 class SelectionManagerDragTest {
 
     private val selectionRegistrar = SelectionRegistrarImpl()
-    private val selectable = FakeSelectable()
+    private val selectableKey = 1L
+    private val selectable = FakeSelectable().also { it.selectableId = this.selectableKey }
     private val selectionManager = SelectionManager(selectionRegistrar)
 
     private val size = IntSize(500, 600)
@@ -62,30 +63,32 @@
     private val endSelectable = mock<Selectable> {
         on { getHandlePosition(any(), any()) } doAnswer Offset.Zero
     }
+    private val startSelectableKey = 2L
+    private val endSelectableKey = 3L
     private val startLayoutCoordinates = mock<LayoutCoordinates>()
     private val endLayoutCoordinates = mock<LayoutCoordinates>()
     private val fakeInitialSelection: Selection = Selection(
         start = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = 0,
-            selectable = startSelectable
+            selectableId = startSelectableKey
         ),
         end = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = 5,
-            selectable = endSelectable
+            selectableId = endSelectableKey
         )
     )
     private val fakeResultSelection: Selection = Selection(
         start = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = 5,
-            selectable = endSelectable
+            selectableId = endSelectableKey
         ),
         end = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = 0,
-            selectable = startSelectable
+            selectableId = startSelectableKey
         )
     )
     private var selection: Selection? = fakeInitialSelection
@@ -94,11 +97,16 @@
 
     @Before
     fun setup() {
-        selectionRegistrar.subscribe(selectable)
-        selectable.selectionToReturn = fakeResultSelection
-
         whenever(startSelectable.getLayoutCoordinates()).thenReturn(startLayoutCoordinates)
+        whenever(startSelectable.selectableId).thenReturn(startSelectableKey)
         whenever(endSelectable.getLayoutCoordinates()).thenReturn(endLayoutCoordinates)
+        whenever(endSelectable.selectableId).thenReturn(endSelectableKey)
+
+        selectionRegistrar.subscribe(selectable)
+        selectionRegistrar.subscribe(startSelectable)
+        selectionRegistrar.subscribe(endSelectable)
+
+        selectable.selectionToReturn = fakeResultSelection
 
         selectionManager.containerLayoutCoordinates = containerLayoutCoordinates
         selectionManager.onSelectionChange = spyLambda
@@ -192,6 +200,7 @@
 }
 
 internal class FakeSelectable : Selectable {
+    override var selectableId = 0L
     var lastStartPosition: Offset? = null
     var lastEndPosition: Offset? = null
     var lastContainerLayoutCoordinates: LayoutCoordinates? = null
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionManagerTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionManagerTest.kt
index 61026bd..83ce467 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionManagerTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionManagerTest.kt
@@ -45,15 +45,32 @@
 class SelectionManagerTest {
     private val selectionRegistrar = spy(SelectionRegistrarImpl())
     private val selectable = FakeSelectable()
+    private val selectableId = 1L
     private val selectionManager = SelectionManager(selectionRegistrar)
 
     private val containerLayoutCoordinates = mock<LayoutCoordinates> {
         on { isAttached } doReturn true
     }
-    private val startSelectable = mock<Selectable>()
-    private val endSelectable = mock<Selectable>()
-    private val middleSelectable = mock<Selectable>()
-    private val lastSelectable = mock<Selectable>()
+
+    private val startSelectableId = 2L
+    private val startSelectable = mock<Selectable> {
+        whenever(it.selectableId).thenReturn(startSelectableId)
+    }
+
+    private val endSelectableId = 3L
+    private val endSelectable = mock<Selectable> {
+        whenever(it.selectableId).thenReturn(endSelectableId)
+    }
+
+    private val middleSelectableId = 4L
+    private val middleSelectable = mock<Selectable> {
+        whenever(it.selectableId).thenReturn(middleSelectableId)
+    }
+
+    private val lastSelectableId = 5L
+    private val lastSelectable = mock<Selectable> {
+        whenever(it.selectableId).thenReturn(lastSelectableId)
+    }
 
     private val startCoordinates = Offset(3f, 30f)
     private val endCoordinates = Offset(3f, 600f)
@@ -63,12 +80,12 @@
             start = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = 0,
-                selectable = startSelectable
+                selectableId = startSelectableId
             ),
             end = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = 5,
-                selectable = endSelectable
+                selectableId = endSelectableId
             )
         )
 
@@ -78,6 +95,7 @@
 
     @Before
     fun setup() {
+        selectable.selectableId = selectableId
         selectionRegistrar.subscribe(selectable)
         selectionManager.containerLayoutCoordinates = containerLayoutCoordinates
         selectionManager.hapticFeedBack = hapticFeedback
@@ -123,8 +141,11 @@
 
     @Test
     fun mergeSelections_multiple_selectables_calls_getSelection_multiple_times() {
-        val selectable_another = mock<Selectable>()
-        selectionRegistrar.subscribe(selectable_another)
+        val anotherSelectableId = 100L
+        val selectableAnother = mock<Selectable>()
+        whenever(selectableAnother.selectableId).thenReturn(anotherSelectableId)
+
+        selectionRegistrar.subscribe(selectableAnother)
 
         selectionManager.mergeSelections(
             startPosition = startCoordinates,
@@ -140,7 +161,7 @@
         assertThat(selectable.lastLongPress).isEqualTo(false)
         assertThat(selectable.lastPreviousSelection).isEqualTo(fakeSelection)
 
-        verify(selectable_another, times(1))
+        verify(selectableAnother, times(1))
             .getSelection(
                 startPosition = startCoordinates,
                 endPosition = endCoordinates,
@@ -190,12 +211,12 @@
             start = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = startOffset,
-                selectable = selectable
+                selectableId = selectableId
             ),
             end = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = endOffset,
-                selectable = selectable
+                selectableId = selectableId
             ),
             handlesCrossed = false
         )
@@ -216,12 +237,12 @@
             start = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = startOffset,
-                selectable = selectable
+                selectableId = selectableId
             ),
             end = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = endOffset,
-                selectable = selectable
+                selectableId = selectableId
             ),
             handlesCrossed = true
         )
@@ -250,12 +271,12 @@
             start = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = startOffset,
-                selectable = startSelectable
+                selectableId = startSelectableId
             ),
             end = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = endOffset,
-                selectable = endSelectable
+                selectableId = endSelectableId
             ),
             handlesCrossed = false
         )
@@ -289,12 +310,12 @@
             start = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = startOffset,
-                selectable = startSelectable
+                selectableId = startSelectableId
             ),
             end = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = endOffset,
-                selectable = endSelectable
+                selectableId = endSelectableId
             ),
             handlesCrossed = true
         )
@@ -329,12 +350,12 @@
             start = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = startOffset,
-                selectable = selectable
+                selectableId = selectableId
             ),
             end = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = endOffset,
-                selectable = selectable
+                selectableId = selectableId
             ),
             handlesCrossed = true
         )
@@ -360,12 +381,12 @@
             start = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = startOffset,
-                selectable = selectable
+                selectableId = selectableId
             ),
             end = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = endOffset,
-                selectable = selectable
+                selectableId = selectableId
             ),
             handlesCrossed = true
         )
@@ -393,12 +414,12 @@
             start = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = startOffset,
-                selectable = selectable
+                selectableId = selectableId
             ),
             end = Selection.AnchorInfo(
                 direction = ResolvedTextDirection.Ltr,
                 offset = endOffset,
-                selectable = selectable
+                selectableId = selectableId
             ),
             handlesCrossed = true
         )
@@ -416,18 +437,18 @@
     }
 
     @Test
-    fun cancel_selection_calls_getSelection_selection_becomes_null() {
+    fun onRelease_selectionMap_is_setToEmpty() {
         val fakeSelection =
             Selection(
                 start = Selection.AnchorInfo(
                     direction = ResolvedTextDirection.Ltr,
                     offset = 0,
-                    selectable = startSelectable
+                    selectableId = startSelectableId
                 ),
                 end = Selection.AnchorInfo(
                     direction = ResolvedTextDirection.Ltr,
                     offset = 5,
-                    selectable = endSelectable
+                    selectableId = endSelectableId
                 )
             )
         var selection: Selection? = fakeSelection
@@ -438,13 +459,7 @@
 
         selectionManager.onRelease()
 
-        assertThat(selectable.getSelectionCalledTimes).isEqualTo(1)
-        assertThat(selectable.lastStartPosition).isEqualTo(Offset(-1f, -1f))
-        assertThat(selectable.lastEndPosition).isEqualTo(Offset(-1f, -1f))
-        assertThat(selectable.lastContainerLayoutCoordinates)
-            .isEqualTo(selectionManager.requireContainerCoordinates())
-        assertThat(selectable.lastLongPress).isEqualTo(false)
-        assertThat(selectable.lastPreviousSelection).isEqualTo(fakeSelection)
+        verify(selectionRegistrar).subselections = emptyMap()
 
         assertThat(selection).isNull()
         verify(spyLambda, times(1)).invoke(null)
@@ -461,12 +476,12 @@
                 start = Selection.AnchorInfo(
                     direction = ResolvedTextDirection.Ltr,
                     offset = 0,
-                    selectable = startSelectable
+                    selectableId = startSelectableId
                 ),
                 end = Selection.AnchorInfo(
                     direction = ResolvedTextDirection.Ltr,
                     offset = 5,
-                    selectable = endSelectable
+                    selectableId = startSelectableId
                 )
             )
         var selection: Selection? = fakeSelection
@@ -475,16 +490,12 @@
         selectionManager.onSelectionChange = spyLambda
         selectionManager.selection = fakeSelection
 
-        selectionRegistrar.notifySelectableChange(selectable)
+        selectionRegistrar.subselections = mapOf(
+            startSelectableId to fakeSelection
+        )
+        selectionRegistrar.notifySelectableChange(startSelectableId)
 
-        assertThat(selectable.getSelectionCalledTimes).isEqualTo(1)
-        assertThat(selectable.lastStartPosition).isEqualTo(Offset(-1f, -1f))
-        assertThat(selectable.lastEndPosition).isEqualTo(Offset(-1f, -1f))
-        assertThat(selectable.lastContainerLayoutCoordinates)
-            .isEqualTo(selectionManager.requireContainerCoordinates())
-        assertThat(selectable.lastLongPress).isEqualTo(false)
-        assertThat(selectable.lastPreviousSelection).isEqualTo(fakeSelection)
-
+        verify(selectionRegistrar).subselections = emptyMap()
         assertThat(selection).isNull()
         verify(spyLambda, times(1)).invoke(null)
         verify(
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImplTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImplTest.kt
index e012c42..d5e37c6 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImplTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionRegistrarImplTest.kt
@@ -30,22 +30,43 @@
 class SelectionRegistrarImplTest {
     @Test
     fun subscribe() {
-        val handler1: Selectable = mock()
-        val handler2: Selectable = mock()
+        val handlerId1 = 1L
+        val handlerId2 = 2L
+        val handler1: Selectable = mockSelectable(handlerId1)
+        val handler2: Selectable = mockSelectable(handlerId2)
+
         val selectionRegistrar = SelectionRegistrarImpl()
 
-        val id1 = selectionRegistrar.subscribe(handler1)
-        val id2 = selectionRegistrar.subscribe(handler2)
+        val key1 = selectionRegistrar.subscribe(handler1)
+        val key2 = selectionRegistrar.subscribe(handler2)
 
-        assertThat(id1).isEqualTo(handler1)
-        assertThat(id2).isEqualTo(handler2)
+        assertThat(key1).isEqualTo(handler1)
+        assertThat(key2).isEqualTo(handler2)
         assertThat(selectionRegistrar.selectables.size).isEqualTo(2)
     }
 
+    @Test(expected = IllegalArgumentException::class)
+    fun subscribe_with_same_key_throws_exception() {
+        val handlerId1 = 1L
+        val handler1: Selectable = mockSelectable(handlerId1)
+
+        val handlerId2 = 1L
+        val handler2: Selectable = mockSelectable(handlerId2)
+
+        val selectionRegistrar = SelectionRegistrarImpl()
+
+        selectionRegistrar.subscribe(handler1)
+        selectionRegistrar.subscribe(handler2)
+    }
+
     @Test
     fun unsubscribe() {
         val handler1: Selectable = mock()
         val handler2: Selectable = mock()
+        val handlerId1 = 1L
+        val handlerId2 = 2L
+        whenever(handler1.selectableId).thenReturn(handlerId1)
+        whenever(handler2.selectableId).thenReturn(handlerId2)
         val selectionRegistrar = SelectionRegistrarImpl()
         selectionRegistrar.subscribe(handler1)
         val id2 = selectionRegistrar.subscribe(handler2)
@@ -57,21 +78,21 @@
 
     @Test
     fun sort() {
-        // Setup.
-        val handler0 = mock<Selectable>()
-        val handler1 = mock<Selectable>()
-        val handler2 = mock<Selectable>()
-        val handler3 = mock<Selectable>()
+        val handlerId0 = 1L
+        val handlerId1 = 2L
+        val handlerId2 = 3L
+        val handlerId3 = 4L
 
         val layoutCoordinates0 = mock<LayoutCoordinates>()
         val layoutCoordinates1 = mock<LayoutCoordinates>()
         val layoutCoordinates2 = mock<LayoutCoordinates>()
         val layoutCoordinates3 = mock<LayoutCoordinates>()
 
-        whenever(handler0.getLayoutCoordinates()).thenReturn(layoutCoordinates0)
-        whenever(handler1.getLayoutCoordinates()).thenReturn(layoutCoordinates1)
-        whenever(handler2.getLayoutCoordinates()).thenReturn(layoutCoordinates2)
-        whenever(handler3.getLayoutCoordinates()).thenReturn(layoutCoordinates3)
+        // Setup.
+        val handler0 = mockSelectable(handlerId0, layoutCoordinates0)
+        val handler1 = mockSelectable(handlerId1, layoutCoordinates1)
+        val handler2 = mockSelectable(handlerId2, layoutCoordinates2)
+        val handler3 = mockSelectable(handlerId3, layoutCoordinates3)
 
         // The order of the 4 handlers should be 1, 0, 3, 2.
         val relativeCoordinates0 = Offset(20f, 12f)
@@ -105,21 +126,21 @@
 
     @Test
     fun unsubscribe_after_sorting() {
-        // Setup.
-        val handler0 = mock<Selectable>()
-        val handler1 = mock<Selectable>()
-        val handler2 = mock<Selectable>()
-        val handler3 = mock<Selectable>()
+        val handlerId0 = 1L
+        val handlerId1 = 2L
+        val handlerId2 = 3L
+        val handlerId3 = 4L
 
         val layoutCoordinates0 = mock<LayoutCoordinates>()
         val layoutCoordinates1 = mock<LayoutCoordinates>()
         val layoutCoordinates2 = mock<LayoutCoordinates>()
         val layoutCoordinates3 = mock<LayoutCoordinates>()
 
-        whenever(handler0.getLayoutCoordinates()).thenReturn(layoutCoordinates0)
-        whenever(handler1.getLayoutCoordinates()).thenReturn(layoutCoordinates1)
-        whenever(handler2.getLayoutCoordinates()).thenReturn(layoutCoordinates2)
-        whenever(handler3.getLayoutCoordinates()).thenReturn(layoutCoordinates3)
+        // Setup.
+        val handler0 = mockSelectable(handlerId0, layoutCoordinates0)
+        val handler1 = mockSelectable(handlerId1, layoutCoordinates1)
+        val handler2 = mockSelectable(handlerId2, layoutCoordinates2)
+        val handler3 = mockSelectable(handlerId3, layoutCoordinates3)
 
         // The order of the 4 handlers should be 1, 0, 3, 2.
         val relativeCoordinates0 = Offset(20f, 12f)
@@ -154,9 +175,9 @@
     @Test
     fun subscribe_after_sorting() {
         // Setup.
-        val handler0 = mock<Selectable>()
+        val handlerId0 = 1L
         val layoutCoordinates0 = mock<LayoutCoordinates>()
-        whenever(handler0.getLayoutCoordinates()).thenReturn(layoutCoordinates0)
+        val handler0 = mockSelectable(handlerId0, layoutCoordinates0)
         val containerLayoutCoordinates = mock<LayoutCoordinates> {
             on { localPositionOf(layoutCoordinates0, Offset.Zero) } doAnswer Offset.Zero
         }
@@ -166,8 +187,10 @@
         selectionRegistrar.sort(containerLayoutCoordinates)
         assertThat(selectionRegistrar.sorted).isTrue()
 
+        val selectableId = 2L
+        val selectable = mockSelectable(selectableId)
         // Act.
-        selectionRegistrar.subscribe(mock())
+        selectionRegistrar.subscribe(selectable)
 
         // Assert.
         assertThat(selectionRegistrar.sorted).isFalse()
@@ -176,9 +199,9 @@
     @Test
     fun layoutCoordinates_changed_after_sorting() {
         // Setup.
-        val handler0 = mock<Selectable>()
+        val handlerId0 = 1L
         val layoutCoordinates0 = mock<LayoutCoordinates>()
-        whenever(handler0.getLayoutCoordinates()).thenReturn(layoutCoordinates0)
+        val handler0 = mockSelectable(handlerId0, layoutCoordinates0)
         val containerLayoutCoordinates = mock<LayoutCoordinates> {
             on { localPositionOf(layoutCoordinates0, Offset.Zero) } doAnswer Offset.Zero
         }
@@ -195,3 +218,15 @@
         assertThat(selectionRegistrar.sorted).isFalse()
     }
 }
+
+private fun mockSelectable(
+    selectableId: Long,
+    layoutCoordinates: LayoutCoordinates? = null
+): Selectable {
+    val selectable: Selectable = mock()
+    whenever(selectable.selectableId).thenReturn(selectableId)
+    layoutCoordinates?.let {
+        whenever(selectable.getLayoutCoordinates()).thenReturn(it)
+    }
+    return selectable
+}
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionTest.kt
index 06173d1..0f93c3b 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/SelectionTest.kt
@@ -19,7 +19,6 @@
 import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.style.ResolvedTextDirection
 import com.google.common.truth.Truth.assertThat
-import com.nhaarman.mockitokotlin2.mock
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -30,33 +29,31 @@
     fun anchorInfo_constructor() {
         val direction = ResolvedTextDirection.Ltr
         val offset = 0
-        val selectable: Selectable = mock()
 
         val anchor = Selection.AnchorInfo(
             direction = direction,
             offset = offset,
-            selectable = selectable
+            selectableId = 1L
         )
 
         assertThat(anchor.direction).isEqualTo(direction)
         assertThat(anchor.offset).isEqualTo(offset)
-        assertThat(anchor.selectable).isEqualTo(selectable)
+        assertThat(anchor.selectableId).isEqualTo(1L)
     }
 
     @Test
     fun selection_constructor() {
         val startOffset = 0
         val endOffset = 6
-        val selectable: Selectable = mock()
         val startAnchor = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = startOffset,
-            selectable = selectable
+            selectableId = 1L
         )
         val endAnchor = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = endOffset,
-            selectable = selectable
+            selectableId = 1L
         )
         val handleCrossed = false
 
@@ -75,16 +72,16 @@
     fun selection_merge_handles_not_cross() {
         val startOffset1 = 9
         val endOffset1 = 20
-        val selectable1: Selectable = mock()
+        val selectableKey1 = 1L
         val startAnchor1 = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = startOffset1,
-            selectable = selectable1
+            selectableId = selectableKey1
         )
         val endAnchor1 = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = endOffset1,
-            selectable = selectable1
+            selectableId = selectableKey1
         )
         val selection1 = Selection(
             start = startAnchor1,
@@ -93,16 +90,16 @@
         )
         val startOffset2 = 0
         val endOffset2 = 30
-        val selectable2: Selectable = mock()
+        val selectableKey2 = 2L
         val startAnchor2 = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = startOffset2,
-            selectable = selectable2
+            selectableId = selectableKey2
         )
         val endAnchor2 = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = endOffset2,
-            selectable = selectable2
+            selectableId = selectableKey2
         )
         val selection2 = Selection(
             start = startAnchor2,
@@ -114,8 +111,8 @@
 
         assertThat(selection.start.offset).isEqualTo(startOffset1)
         assertThat(selection.end.offset).isEqualTo(endOffset2)
-        assertThat(selection.start.selectable).isEqualTo(selectable1)
-        assertThat(selection.end.selectable).isEqualTo(selectable2)
+        assertThat(selection.start.selectableId).isEqualTo(selectableKey1)
+        assertThat(selection.end.selectableId).isEqualTo(selectableKey2)
         assertThat(selection.handlesCrossed).isFalse()
     }
 
@@ -123,16 +120,16 @@
     fun selection_merge_handles_cross() {
         val startOffset1 = 20
         val endOffset1 = 9
-        val selectable1: Selectable = mock()
+        val selectableKey1 = 1L
         val startAnchor1 = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = startOffset1,
-            selectable = selectable1
+            selectableId = selectableKey1
         )
         val endAnchor1 = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = endOffset1,
-            selectable = selectable1
+            selectableId = selectableKey1
         )
         val selection1 = Selection(
             start = startAnchor1,
@@ -141,16 +138,16 @@
         )
         val startOffset2 = 30
         val endOffset2 = 0
-        val selectable2: Selectable = mock()
+        val selectableKey2 = 2L
         val startAnchor2 = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = startOffset2,
-            selectable = selectable2
+            selectableId = selectableKey2
         )
         val endAnchor2 = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = endOffset2,
-            selectable = selectable2
+            selectableId = selectableKey2
         )
         val selection2 = Selection(
             start = startAnchor2,
@@ -162,8 +159,8 @@
 
         assertThat(selection.start.offset).isEqualTo(startOffset2)
         assertThat(selection.end.offset).isEqualTo(endOffset1)
-        assertThat(selection.start.selectable).isEqualTo(selectable2)
-        assertThat(selection.end.selectable).isEqualTo(selectable1)
+        assertThat(selection.start.selectableId).isEqualTo(selectableKey2)
+        assertThat(selection.end.selectableId).isEqualTo(selectableKey1)
         assertThat(selection.handlesCrossed).isTrue()
     }
 
@@ -174,12 +171,12 @@
         val startAnchor = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = startOffset,
-            selectable = mock()
+            selectableId = 1L
         )
         val endAnchor = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = endOffset,
-            selectable = mock()
+            selectableId = 1L
         )
         val selection = Selection(
             start = startAnchor,
@@ -199,12 +196,12 @@
         val startAnchor = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = startOffset,
-            selectable = mock()
+            selectableId = 1L
         )
         val endAnchor = Selection.AnchorInfo(
             direction = ResolvedTextDirection.Ltr,
             offset = endOffset,
-            selectable = mock()
+            selectableId = 1L
         )
         val selection = Selection(
             start = startAnchor,
diff --git a/compose/integration-tests/benchmark/lint-baseline.xml b/compose/integration-tests/benchmark/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/integration-tests/benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/integration-tests/demos/build.gradle b/compose/integration-tests/demos/build.gradle
index 1e0b1bd..6c0311c 100644
--- a/compose/integration-tests/demos/build.gradle
+++ b/compose/integration-tests/demos/build.gradle
@@ -16,7 +16,6 @@
     implementation(project(":compose:foundation:foundation:integration-tests:foundation-demos"))
     implementation(project(":compose:material:material:integration-tests:material-demos"))
     implementation(project(":compose:material:material:integration-tests:material-catalog"))
-    implementation(project(":compose:material:material:integration-tests:material-studies"))
     implementation(project(":navigation:navigation-compose:integration-tests:navigation-demos"))
     implementation(project(":compose:ui:ui:integration-tests:ui-demos"))
 
diff --git a/compose/integration-tests/demos/common/lint-baseline.xml b/compose/integration-tests/demos/common/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/integration-tests/demos/common/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/integration-tests/demos/lint-baseline.xml b/compose/integration-tests/demos/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/integration-tests/demos/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/Demos.kt b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/Demos.kt
index 89c5a75..98cd9aa 100644
--- a/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/Demos.kt
+++ b/compose/integration-tests/demos/src/main/java/androidx/compose/integration/demos/Demos.kt
@@ -24,7 +24,6 @@
 import androidx.compose.foundation.demos.text.TextDemos
 import androidx.compose.material.catalog.MaterialCatalog
 import androidx.compose.material.demos.MaterialDemos
-import androidx.compose.material.studies.MaterialStudies
 import androidx.compose.ui.demos.CoreDemos
 import androidx.navigation.compose.demos.NavigationDemos
 
@@ -41,7 +40,6 @@
         LayoutDemos,
         MaterialDemos,
         MaterialCatalog,
-        MaterialStudies,
         NavigationDemos,
         TextDemos
     )
diff --git a/compose/integration-tests/docs-snippets/lint-baseline.xml b/compose/integration-tests/docs-snippets/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/integration-tests/docs-snippets/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/animation/Animation.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/animation/Animation.kt
index 4b2b047..f674d16 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/animation/Animation.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/animation/Animation.kt
@@ -101,7 +101,6 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.dp
-import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 import org.junit.Rule
@@ -304,7 +303,7 @@
 }
 
 @Composable
-fun TargetBasedAnimationSimple(someCustomCondition: () -> Boolean, scope: CoroutineScope) {
+fun TargetBasedAnimationSimple(someCustomCondition: () -> Boolean) {
     val anim = remember {
         TargetBasedAnimation(
             animationSpec = tween(200),
@@ -315,7 +314,7 @@
     }
     var playTime by remember { mutableStateOf(0L) }
 
-    scope.launch {
+    LaunchedEffect(anim) {
         val startTime = withFrameNanos { it }
 
         do {
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/resources/Resources.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/resources/Resources.kt
index 75e7d481..62430ab 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/resources/Resources.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/resources/Resources.kt
@@ -149,6 +149,7 @@
     Icon(Icons.Rounded.Menu, contentDescription = "Localized description")
 }
 
+@Suppress("UnnecessaryLambdaCreation")
 private object ResourcesSnippet9 {
     // Define and load the fonts of the app
     private val light = Font(R.font.raleway_light, FontWeight.W300)
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/testing/CheatSheet.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/testing/CheatSheet.kt
index 3feae6b..14bfe86e 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/testing/CheatSheet.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/testing/CheatSheet.kt
@@ -371,7 +371,6 @@
 }
 
 @RequiresApi(Build.VERSION_CODES.O)
-@Composable
 private fun TestingCheatSheetOther() {
 
     // COMPOSE TEST RULE
diff --git a/compose/integration-tests/lint-baseline.xml b/compose/integration-tests/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/integration-tests/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/integration-tests/macrobenchmark-target/lint-baseline.xml b/compose/integration-tests/macrobenchmark-target/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/integration-tests/macrobenchmark-target/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/integration-tests/macrobenchmark/lint-baseline.xml b/compose/integration-tests/macrobenchmark/lint-baseline.xml
deleted file mode 100644
index 8f1aa4b..0000000
--- a/compose/integration-tests/macrobenchmark/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-</issues>
diff --git a/compose/internal-lint-checks/lint-baseline.xml b/compose/internal-lint-checks/lint-baseline.xml
deleted file mode 100644
index 297ae16..0000000
--- a/compose/internal-lint-checks/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" version="4.2.0-beta02">
-
-</issues>
diff --git a/compose/internal-lint-checks/build.gradle b/compose/lint/common/build.gradle
similarity index 71%
copy from compose/internal-lint-checks/build.gradle
copy to compose/lint/common/build.gradle
index 403eb2e..feb7618 100644
--- a/compose/internal-lint-checks/build.gradle
+++ b/compose/lint/common/build.gradle
@@ -18,23 +18,20 @@
 
 import static androidx.build.dependencies.DependenciesKt.*
 
-buildscript {
-    dependencies {
-        classpath "com.github.jengelman.gradle.plugins:shadow:4.0.4"
-    }
-}
-
 plugins {
     id("AndroidXPlugin")
     id("kotlin")
 }
 
-apply(plugin:"com.github.johnrengelman.shadow")
-
 dependencies {
-    compileOnly(LINT_API_LATEST)
+    if (rootProject.hasProperty("android.injected.invoked.from.ide")) {
+        compileOnly(LINT_API_LATEST)
+    } else {
+        compileOnly(LINT_API_MIN)
+    }
     compileOnly(KOTLIN_STDLIB)
-    api(project(":lint-checks"))
+
+    api(KOTLIN_METADATA_JVM)
 
     testImplementation(KOTLIN_STDLIB)
     testImplementation(LINT_CORE)
@@ -42,10 +39,8 @@
 }
 
 androidx {
-    name = "Compose internal lint checks"
+    name = "Compose Lint Utils"
     type = LibraryType.LINT
-    inceptionYear = "2019"
-    description = "Internal lint checks for Compose"
+    inceptionYear = "2021"
+    description = "Lint utils used for writing Compose related lint checks"
 }
-
-tasks["shadowJar"].archiveFileName = "merged.jar"
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt b/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt
new file mode 100644
index 0000000..7334704
--- /dev/null
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/ComposableUtils.kt
@@ -0,0 +1,183 @@
+/*
+ * 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.lint
+
+import com.intellij.psi.impl.compiled.ClsMethodImpl
+import com.intellij.psi.impl.compiled.ClsParameterImpl
+import kotlinx.metadata.jvm.annotations
+import org.jetbrains.kotlin.psi.KtAnnotated
+import org.jetbrains.kotlin.psi.KtFunction
+import org.jetbrains.kotlin.psi.KtTypeReference
+import org.jetbrains.uast.UAnnotation
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.ULambdaExpression
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UParameter
+import org.jetbrains.uast.UTypeReferenceExpression
+import org.jetbrains.uast.UVariable
+import org.jetbrains.uast.getContainingDeclaration
+import org.jetbrains.uast.getContainingUMethod
+import org.jetbrains.uast.getParameterForArgument
+import org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression
+import org.jetbrains.uast.toUElement
+import org.jetbrains.uast.withContainingElements
+
+/**
+ * Returns whether this [UCallExpression] is invoked within the body of a Composable function or
+ * lambda.
+ *
+ * This searches parent declarations until we find a lambda expression or a function, and looks
+ * to see if these are Composable. Additionally, if we are inside a non-Composable lambda, the
+ * lambda is a parameter on an inline function, and the inline function is within a Composable
+ * lambda / function, this will also return true - since scoping functions / iterator functions
+ * are commonly used within Composables.
+ */
+fun UCallExpression.isInvokedWithinComposable(): Boolean {
+    // The nearest property / function / etc declaration that contains this call expression
+    val containingDeclaration = getContainingDeclaration()
+
+    // Look through containing elements until we find a lambda or a method
+    for (element in withContainingElements) {
+        when (element) {
+            is ULambdaExpression -> {
+                if (element.isComposable) {
+                    return true
+                }
+                val parent = element.uastParent
+                if (parent is KotlinUFunctionCallExpression && parent.isDeclarationInline) {
+                    // We are now in a non-composable lambda parameter inside an inline function
+                    // For example, a scoping function such as run {} or apply {} - since the
+                    // body will be inlined and this is a common case, try to see if there is
+                    // a parent composable function above us, since it is still most likely
+                    // an error to call these methods inside an inline function, inside a
+                    // Composable function.
+                    continue
+                } else {
+                    return false
+                }
+            }
+            is UMethod -> {
+                return element.isComposable
+            }
+            // Stop when we reach the parent declaration to avoid escaping the scope. This
+            // shouldn't be called unless there is a UAST type we don't handle above.
+            containingDeclaration -> return false
+        }
+    }
+    return false
+}
+
+// TODO: https://youtrack.jetbrains.com/issue/KT-45406
+// KotlinUMethodWithFakeLightDelegate.hasAnnotation() (for reified functions for example)
+// doesn't find annotations, so just look at the annotations directly.
+// Note: annotations is deprecated but the replacement uAnnotations isn't available on the
+// version of lint / uast we compile against, shouldn't be an issue when the above issue is fixed.
+/**
+ * Returns whether this method is @Composable or not
+ */
+@Suppress("DEPRECATION")
+val UMethod.isComposable
+    get() = annotations.any { it.qualifiedName == Names.Runtime.Composable.javaFqn }
+
+/**
+ * Returns whether this variable's type is @Composable or not
+ */
+val UVariable.isComposable: Boolean
+    get() {
+        // Annotation on the lambda
+        val annotationOnLambda = when (val initializer = uastInitializer) {
+            is ULambdaExpression -> {
+                val source = initializer.sourcePsi
+                if (source is KtFunction) {
+                    // Anonymous function, val foo = @Composable fun() {}
+                    source.hasComposableAnnotation
+                } else {
+                    // Lambda, val foo = @Composable {}
+                    initializer.findAnnotation(Names.Runtime.Composable.javaFqn) != null
+                }
+            }
+            else -> false
+        }
+        // Annotation on the type, foo: @Composable () -> Unit = { }
+        val annotationOnType = typeReference?.isComposable == true
+        return annotationOnLambda || annotationOnType
+    }
+
+/**
+ * Returns whether this parameter's type is @Composable or not
+ */
+val UParameter.isComposable: Boolean
+    get() = when (sourcePsi) {
+        // The parameter is in a class file. Currently type annotations aren't currently added to
+        // the underlying type (https://youtrack.jetbrains.com/issue/KT-45307), so instead we use
+        // the metadata annotation.
+        is ClsParameterImpl -> {
+            // Find the containing method, so we can get metadata from the containing class
+            val containingMethod = getContainingUMethod()!!.sourcePsi as ClsMethodImpl
+            val kmFunction = containingMethod.toKmFunction()
+
+            val kmValueParameter = kmFunction?.valueParameters?.find {
+                it.name == name
+            }
+
+            kmValueParameter?.type?.annotations?.find {
+                it.className == Names.Runtime.Composable.kmClassName
+            } != null
+        }
+        // The parameter is in a source declaration
+        else -> typeReference!!.isComposable
+    }
+
+/**
+ * Returns whether this lambda expression is @Composable or not
+ */
+val ULambdaExpression.isComposable: Boolean
+    get() = when (val lambdaParent = uastParent) {
+        // Function call with a lambda parameter
+        is UCallExpression -> {
+            val parameter = lambdaParent.getParameterForArgument(this)
+            (parameter.toUElement() as? UParameter)?.isComposable == true
+        }
+        // A local / non-local lambda variable
+        is UVariable -> {
+            lambdaParent.isComposable
+        }
+        // Either a new UAST type we haven't handled, or non-Kotlin declarations
+        else -> false
+    }
+
+/**
+ * Returns whether this type reference is @Composable or not
+ */
+private val UTypeReferenceExpression.isComposable: Boolean
+    get() {
+        if (type.hasAnnotation(Names.Runtime.Composable.javaFqn)) return true
+
+        // Annotations should be available on the PsiType itself in 1.4.30+, but we are
+        // currently on an older version of UAST / Kotlin embedded compiled
+        // (https://youtrack.jetbrains.com/issue/KT-45244), so we need to manually check the
+        // underlying type reference. Until then, the above check will always fail.
+        return (sourcePsi as? KtTypeReference)?.hasComposableAnnotation == true
+    }
+
+/**
+ * Returns whether this annotated declaration has a Composable annotation
+ */
+private val KtAnnotated.hasComposableAnnotation: Boolean
+    get() = annotationEntries.any {
+        (it.toUElement() as UAnnotation).qualifiedName == Names.Runtime.Composable.javaFqn
+    }
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/KotlinMetadataUtils.kt b/compose/lint/common/src/main/java/androidx/compose/lint/KotlinMetadataUtils.kt
new file mode 100644
index 0000000..347b842
--- /dev/null
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/KotlinMetadataUtils.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.lint
+
+import com.intellij.lang.jvm.annotation.JvmAnnotationArrayValue
+import com.intellij.lang.jvm.annotation.JvmAnnotationAttributeValue
+import com.intellij.lang.jvm.annotation.JvmAnnotationConstantValue
+import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.impl.compiled.ClsMemberImpl
+import com.intellij.psi.impl.compiled.ClsMethodImpl
+import com.intellij.psi.util.ClassUtil
+import kotlinx.metadata.KmDeclarationContainer
+import kotlinx.metadata.KmFunction
+import kotlinx.metadata.jvm.KotlinClassHeader
+import kotlinx.metadata.jvm.KotlinClassMetadata
+import kotlinx.metadata.jvm.signature
+
+/**
+ * @return the corresponding [KmFunction] for this [ClsMethodImpl], or `null` if there is no
+ * corresponding [KmFunction].
+ */
+fun ClsMethodImpl.toKmFunction(): KmFunction? =
+    getKmDeclarationContainer()?.findKmFunctionForPsiMethod(this)
+
+// TODO: https://youtrack.jetbrains.com/issue/KT-45310
+// Currently there is no built in support for parsing kotlin metadata from kotlin class files, so
+// we need to manually inspect the annotations and work with Cls* (compiled PSI).
+/**
+ * Returns the [KmDeclarationContainer] using the kotlin.Metadata annotation present on the
+ * surrounding class. Returns null if there is no surrounding annotation (not parsing a Kotlin
+ * class file), the annotation data is for an unsupported version of Kotlin, or if the metadata
+ * represents a synthetic class.
+ */
+private fun ClsMemberImpl<*>.getKmDeclarationContainer(): KmDeclarationContainer? {
+    val classKotlinMetadataAnnotation = containingClass?.annotations?.find {
+        // hasQualifiedName() not available on the min version of Lint we compile against
+        it.qualifiedName == KotlinMetadataFqn
+    } ?: return null
+
+    val metadata = KotlinClassMetadata.read(classKotlinMetadataAnnotation.toHeader())
+        ?: return null
+
+    return when (metadata) {
+        is KotlinClassMetadata.Class -> metadata.toKmClass()
+        is KotlinClassMetadata.FileFacade -> metadata.toKmPackage()
+        is KotlinClassMetadata.SyntheticClass -> null
+        is KotlinClassMetadata.MultiFileClassFacade -> null
+        is KotlinClassMetadata.MultiFileClassPart -> metadata.toKmPackage()
+        is KotlinClassMetadata.Unknown -> null
+    }
+}
+
+/**
+ * Returns a [KotlinClassHeader] by parsing the attributes of this @kotlin.Metadata annotation.
+ *
+ * See: https://github.com/udalov/kotlinx-metadata-examples/blob/master/src/main/java
+ * /examples/FindKotlinGeneratedMethods.java
+ */
+private fun PsiAnnotation.toHeader(): KotlinClassHeader {
+    val attributes = attributes.associate { it.attributeName to it.attributeValue }
+
+    fun JvmAnnotationAttributeValue.parseString(): String =
+        (this as JvmAnnotationConstantValue).constantValue as String
+
+    fun JvmAnnotationAttributeValue.parseInt(): Int =
+        (this as JvmAnnotationConstantValue).constantValue as Int
+
+    fun JvmAnnotationAttributeValue.parseStringArray(): Array<String> =
+        (this as JvmAnnotationArrayValue).values.map {
+            it.parseString()
+        }.toTypedArray()
+
+    fun JvmAnnotationAttributeValue.parseIntArray(): IntArray =
+        (this as JvmAnnotationArrayValue).values.map {
+            it.parseInt()
+        }.toTypedArray().toIntArray()
+
+    val kind = attributes["k"]?.parseInt()
+    val metadataVersion = attributes["mv"]?.parseIntArray()
+    val bytecodeVersion = attributes["bv"]?.parseIntArray()
+    val data1 = attributes["d1"]?.parseStringArray()
+    val data2 = attributes["d2"]?.parseStringArray()
+    val extraString = attributes["xs"]?.parseString()
+    val packageName = attributes["pn"]?.parseString()
+    val extraInt = attributes["xi"]?.parseInt()
+
+    return KotlinClassHeader(
+        kind,
+        metadataVersion,
+        bytecodeVersion,
+        data1,
+        data2,
+        extraString,
+        packageName,
+        extraInt
+    )
+}
+
+/**
+ * @return the corresponding [KmFunction] in [this] for the given [method], matching by name and
+ * signature.
+ */
+private fun KmDeclarationContainer.findKmFunctionForPsiMethod(method: PsiMethod): KmFunction? {
+    val expectedName = method.name
+    val expectedSignature = ClassUtil.getAsmMethodSignature(method)
+
+    return functions.find {
+        it.name == expectedName && it.signature?.desc == expectedSignature
+    }
+}
+
+private const val KotlinMetadataFqn = "kotlin.Metadata"
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/KotlinUtils.kt b/compose/lint/common/src/main/java/androidx/compose/lint/KotlinUtils.kt
new file mode 100644
index 0000000..4b20849
--- /dev/null
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/KotlinUtils.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.lint
+
+import com.intellij.psi.impl.compiled.ClsMethodImpl
+import kotlinx.metadata.Flag
+import org.jetbrains.kotlin.lexer.KtTokens.INLINE_KEYWORD
+import org.jetbrains.kotlin.psi.KtFunction
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.resolveToUElement
+
+/**
+ * @return whether the resolved declaration for this call expression is an inline function
+ */
+val UCallExpression.isDeclarationInline: Boolean
+    get() {
+        return when (val source = resolveToUElement()?.sourcePsi) {
+            // Parsing a method defined in a class file
+            is ClsMethodImpl -> {
+                val flags = source.toKmFunction()?.flags ?: return false
+                return Flag.Function.IS_INLINE(flags)
+            }
+            // Parsing a method defined in Kotlin source
+            is KtFunction -> {
+                source.hasModifier(INLINE_KEYWORD)
+            }
+            // Parsing another declaration (such as a property) which cannot be inline, or
+            // a non-Kotlin declaration
+            else -> false
+        }
+    }
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt b/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
new file mode 100644
index 0000000..4ff61a8
--- /dev/null
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/Names.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.lint
+
+import kotlinx.metadata.ClassName
+
+/**
+ * Contains common names used for lint checks.
+ */
+object Names {
+    object Runtime {
+        val PackageName = Package("androidx.compose.runtime")
+
+        val Composable = Name(PackageName, "Composable")
+        val CompositionLocal = Name(PackageName, "CompositionLocal")
+        val Remember = Name(PackageName, "remember")
+    }
+    object Ui {
+        val PackageName = Package("androidx.compose.ui")
+        val Modifier = Name(PackageName, "Modifier")
+    }
+}
+
+/**
+ * Represents a qualified package
+ *
+ * @property segments the segments representing the package
+ */
+class PackageName internal constructor(internal val segments: List<String>) {
+    /**
+     * The Java-style package name for this [Name], separated with `.`
+     */
+    val javaPackageName: String
+        get() = segments.joinToString(".")
+}
+
+/**
+ * Represents the qualified name for an element
+ *
+ * @property pkg the package for this element
+ * @property nameSegments the segments representing the element - there can be multiple in the
+ * case of nested classes.
+ */
+class Name internal constructor(
+    private val pkg: PackageName,
+    private val nameSegments: List<String>
+) {
+    /**
+     * The short name for this [Name]
+     */
+    val shortName: String
+        get() = nameSegments.last()
+
+    /**
+     * The Java-style fully qualified name for this [Name], separated with `.`
+     */
+    val javaFqn: String
+        get() = pkg.segments.joinToString(".", postfix = ".") +
+            nameSegments.joinToString(".")
+
+    /**
+     * The [ClassName] for use with kotlinx.metadata. Note that in kotlinx.metadata the actual
+     * type might be different from the underlying JVM type, for example:
+     * kotlin/Int -> java/lang/Integer
+     */
+    val kmClassName: ClassName
+        get() = pkg.segments.joinToString("/", postfix = "/") +
+            nameSegments.joinToString(".")
+}
+
+/**
+ * @return a [PackageName] with a Java-style (separated with `.`) [packageName].
+ */
+fun Package(packageName: String): PackageName =
+    PackageName(packageName.split("."))
+
+/**
+ * @return a [Name] with the provided [pkg] and Java-style (separated with `.`) [shortName].
+ */
+fun Name(pkg: PackageName, shortName: String): Name =
+    Name(pkg, shortName.split("."))
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/PsiUtils.kt b/compose/lint/common/src/main/java/androidx/compose/lint/PsiUtils.kt
new file mode 100644
index 0000000..ccb3a74
--- /dev/null
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/PsiUtils.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.lint
+
+import com.intellij.psi.PsiJavaFile
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiType
+
+/**
+ * Returns whether [this] has [packageName] as its package name.
+ */
+fun PsiMethod.isInPackageName(packageName: PackageName): Boolean {
+    val actual = (containingFile as? PsiJavaFile)?.packageName
+    return packageName.javaPackageName == actual
+}
+
+/**
+ * Whether this [PsiMethod] returns Unit
+ */
+val PsiMethod.returnsUnit
+    get() = returnType == PsiType.VOID
diff --git a/compose/lint/common/src/main/java/androidx/compose/lint/Stubs.kt b/compose/lint/common/src/main/java/androidx/compose/lint/Stubs.kt
new file mode 100644
index 0000000..05695f4
--- /dev/null
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/Stubs.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.lint
+
+import org.intellij.lang.annotations.Language
+
+/**
+ * Common Compose-related lint stubs used for testing
+ */
+object Stubs {
+    val Color = stub(
+        """
+            package androidx.compose.ui.graphics
+
+            inline class Color(val value: ULong) {
+                companion object {
+                    val Black = Color(0xFF000000)
+                    val DarkGray = Color(0xFF444444)
+                    val Gray = Color(0xFF888888)
+                    val LightGray = Color(0xFFCCCCCC)
+                    val White = Color(0xFFFFFFFF)
+                    val Red = Color(0xFFFF0000)
+                    val Green = Color(0xFF00FF00)
+                    val Blue = Color(0xFF0000FF)
+                    val Yellow = Color(0xFFFFFF00)
+                    val Cyan = Color(0xFF00FFFF)
+                    val Magenta = Color(0xFFFF00FF)
+                    val Transparent = Color(0x00000000)
+                }
+            }
+
+            fun Color(color: Long): Color {
+                return Color(value = (color.toULong() and 0xffffffffUL) shl 32)
+            }
+        """
+    )
+
+    val Composable = stub(
+        """
+        package androidx.compose.runtime
+
+        @MustBeDocumented
+        @Retention(AnnotationRetention.BINARY)
+        @Target(
+            AnnotationTarget.FUNCTION,
+            AnnotationTarget.TYPE,
+            AnnotationTarget.TYPE_PARAMETER,
+            AnnotationTarget.PROPERTY
+        )
+        annotation class Composable
+        """
+    )
+
+    val Modifier = stub(
+        """
+        package androidx.compose.ui
+
+        import androidx.compose.ui.platform.InspectorInfo
+        import androidx.compose.ui.platform.InspectorValueInfo
+
+        @Suppress("ModifierFactoryExtensionFunction")
+        interface Modifier {
+            infix fun then(other: Modifier): Modifier =
+                if (other === Modifier) this else CombinedModifier(this, other)
+
+            interface Element : Modifier
+
+            companion object : Modifier {
+                override infix fun then(other: Modifier): Modifier = other
+            }
+        }
+
+        class CombinedModifier(
+            private val outer: Modifier,
+            private val inner: Modifier
+        ) : Modifier
+        """
+    )
+
+    val Remember = stub(
+        """
+        package androidx.compose.runtime
+
+        import androidx.compose.runtime.Composable
+
+        @Composable
+        inline fun <T> remember(calculation: () -> T): T = calculation()
+
+        @Composable
+        inline fun <T, V1> remember(
+            v1: V1,
+            calculation: () -> T
+        ): T = calculation()
+
+        @Composable
+        inline fun <T, V1, V2> remember(
+            v1: V1,
+            v2: V2,
+            calculation: () -> T
+        ): T = calculation()
+
+        @Composable
+        inline fun <T, V1, V2, V3> remember(
+            v1: V1,
+            v2: V2,
+            v3: V3,
+            calculation: () -> T
+        ): T = calculation()
+
+        @Composable
+        inline fun <V> remember(
+            vararg inputs: Any?,
+            calculation: () -> V
+        ): V = calculation()
+        """
+    )
+}
+
+// @Language isn't available as a type annotation, so we need a parameter
+private fun stub(@Language("kotlin") code: String) = code
\ No newline at end of file
diff --git a/compose/internal-lint-checks/build.gradle b/compose/lint/internal-lint-checks/build.gradle
similarity index 93%
rename from compose/internal-lint-checks/build.gradle
rename to compose/lint/internal-lint-checks/build.gradle
index 403eb2e..83bcf45 100644
--- a/compose/internal-lint-checks/build.gradle
+++ b/compose/lint/internal-lint-checks/build.gradle
@@ -35,6 +35,7 @@
     compileOnly(LINT_API_LATEST)
     compileOnly(KOTLIN_STDLIB)
     api(project(":lint-checks"))
+    implementation(project(":compose:lint:common"))
 
     testImplementation(KOTLIN_STDLIB)
     testImplementation(LINT_CORE)
@@ -42,7 +43,7 @@
 }
 
 androidx {
-    name = "Compose internal lint checks"
+    name = "Compose Internal Lint Checks"
     type = LibraryType.LINT
     inceptionYear = "2019"
     description = "Internal lint checks for Compose"
diff --git a/compose/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
similarity index 93%
rename from compose/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
rename to compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
index a5803b3..5dbf2d7 100644
--- a/compose/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("UnstableApiUsage")
+
 package androidx.compose.lint
 
 import androidx.build.lint.AndroidXIssueRegistry
@@ -26,6 +28,7 @@
     override val api = 8
     override val issues get(): List<Issue> {
         return listOf(
+            ListIteratorDetector.ISSUE,
             ModifierInspectorInfoDetector.ISSUE,
             UnnecessaryLambdaCreationDetector.ISSUE,
         ) + AndroidXIssueRegistry.Issues
diff --git a/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ListIteratorDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ListIteratorDetector.kt
new file mode 100644
index 0000000..7e22dda
--- /dev/null
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ListIteratorDetector.kt
@@ -0,0 +1,152 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.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 com.intellij.psi.impl.compiled.ClsMethodImpl
+import com.intellij.psi.util.InheritanceUtil
+import kotlinx.metadata.KmClassifier
+import org.jetbrains.kotlin.psi.KtForExpression
+import org.jetbrains.kotlin.psi.KtNamedFunction
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UForEachExpression
+import org.jetbrains.uast.UTypeReferenceExpression
+import org.jetbrains.uast.resolveToUElement
+import org.jetbrains.uast.toUElement
+
+/**
+ * Lint [Detector] to prevent allocating Iterators when iterating on a [List]. Instead of using
+ * `for (e in list)` or `list.forEach {}`, more efficient iteration methods should be used, such as
+ * `for (i in list.indices) { list[i]... }` or `list.fastForEach`.
+ */
+class ListIteratorDetector : Detector(), SourceCodeScanner {
+    override fun getApplicableUastTypes() = listOf(
+        UForEachExpression::class.java,
+        UCallExpression::class.java
+    )
+
+    override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
+        override fun visitForEachExpression(node: UForEachExpression) {
+            // Type of the variable we are iterating on, i.e the type of `b` in `for (a in b)`
+            val iteratedValueType = node.iteratedValue.getExpressionType()
+            // We are iterating on a List
+            if (InheritanceUtil.isInheritor(iteratedValueType, JavaList.javaFqn)) {
+                // Find the `in` keyword to use as location
+                val inKeyword = (node.sourcePsi as? KtForExpression)?.inKeyword
+                val location = if (inKeyword == null) {
+                    context.getNameLocation(node)
+                } else {
+                    context.getNameLocation(inKeyword)
+                }
+                context.report(
+                    ISSUE,
+                    node,
+                    location,
+                    "Creating an unnecessary Iterator to iterate through a List"
+                )
+            }
+        }
+
+        override fun visitCallExpression(node: UCallExpression) {
+            val receiverType = node.receiverType
+
+            // We are calling a method on a `List` type
+            if (receiverType != null &&
+                InheritanceUtil.isInheritor(node.receiverType, JavaList.javaFqn)
+            ) {
+                when (val method = node.resolveToUElement()?.sourcePsi) {
+                    // Parsing a class file
+                    is ClsMethodImpl -> {
+                        method.checkForIterableReceiver(node)
+                    }
+                    // Parsing Kotlin source
+                    is KtNamedFunction -> {
+                        method.checkForIterableReceiver(node)
+                    }
+                }
+            }
+        }
+
+        private fun ClsMethodImpl.checkForIterableReceiver(node: UCallExpression) {
+            val kmFunction = this.toKmFunction()
+
+            kmFunction?.let {
+                if (it.receiverParameterType?.classifier == KotlinIterableClassifier) {
+                    context.report(
+                        ISSUE,
+                        node,
+                        context.getNameLocation(node),
+                        "Creating an unnecessary Iterator to iterate through a List"
+                    )
+                }
+            }
+        }
+
+        private fun KtNamedFunction.checkForIterableReceiver(node: UCallExpression) {
+            val receiver = receiverTypeReference
+            // If there is no receiver, or the receiver isn't an Iterable, ignore
+            if ((receiver.toUElement() as? UTypeReferenceExpression)
+                ?.getQualifiedName() != JavaIterable.javaFqn
+            ) return
+
+            context.report(
+                ISSUE,
+                node,
+                context.getNameLocation(node),
+                "Creating an unnecessary Iterator to iterate through a List"
+            )
+        }
+    }
+
+    companion object {
+        val ISSUE = Issue.create(
+            "ListIterator",
+            "Creating an unnecessary Iterator to iterate through a List",
+            "Iterable<T> extension methods and using `for (a in list)` will create an " +
+                "Iterator object - in hot code paths this can cause a lot of extra allocations " +
+                "which is something we want to avoid. Instead, use a method that doesn't " +
+                "allocate, such as `fastForEach`, or use `for (a in list.indices)` as iterating " +
+                "through an `IntRange` does not allocate an Iterator, and becomes just a simple " +
+                "for loop.",
+            Category.PERFORMANCE, 5, Severity.ERROR,
+            Implementation(
+                ListIteratorDetector::class.java,
+                Scope.JAVA_FILE_SCOPE
+            )
+        )
+    }
+}
+
+// Kotlin collections on JVM are just the underlying Java collections
+private val JavaLangPackageName = Package("java.lang")
+private val JavaUtilPackageName = Package("java.util")
+private val JavaList = Name(JavaUtilPackageName, "List")
+private val JavaIterable = Name(JavaLangPackageName, "Iterable")
+
+private val KotlinCollectionsPackageName = Package("kotlin.collections")
+private val KotlinIterable = Name(KotlinCollectionsPackageName, "Iterable")
+private val KotlinIterableClassifier = KmClassifier.Class(KotlinIterable.kmClassName)
\ No newline at end of file
diff --git a/compose/internal-lint-checks/src/main/java/androidx/compose/lint/ModifierInspectorInfoDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ModifierInspectorInfoDetector.kt
similarity index 100%
rename from compose/internal-lint-checks/src/main/java/androidx/compose/lint/ModifierInspectorInfoDetector.kt
rename to compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/ModifierInspectorInfoDetector.kt
diff --git a/compose/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt
similarity index 66%
rename from compose/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt
rename to compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt
index 3806120..2f4a022 100644
--- a/compose/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt
+++ b/compose/lint/internal-lint-checks/src/main/java/androidx/compose/lint/UnnecessaryLambdaCreationDetector.kt
@@ -27,20 +27,14 @@
 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 com.android.tools.lint.detector.api.UastLintUtils.Companion.tryResolveUDeclaration
 import com.intellij.psi.impl.source.PsiClassReferenceType
-import org.jetbrains.kotlin.psi.KtCallExpression
-import org.jetbrains.kotlin.psi.KtCallableDeclaration
-import org.jetbrains.kotlin.psi.KtTypeReference
-import org.jetbrains.kotlin.psi.psiUtil.referenceExpression
-import org.jetbrains.uast.UAnnotation
 import org.jetbrains.uast.ULambdaExpression
-import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UVariable
 import org.jetbrains.uast.kotlin.KotlinUBlockExpression
 import org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression
 import org.jetbrains.uast.kotlin.KotlinUImplicitReturnExpression
-import org.jetbrains.uast.resolveToUElement
 import org.jetbrains.uast.toUElement
-import org.jetbrains.uast.tryResolve
 
 /**
  * Lint [Detector] to ensure that we are not creating extra lambdas just to emit already captured
@@ -129,56 +123,18 @@
             // shouldn't matter that much in practice.
             if (functionType.reference.canonicalText.contains(NonExistentClass)) return
 
-            // Component nodes are classes that are invoked as if they are a function call, but
-            // they aren't actually a function call and so they cannot be inlined. Unfortunately
-            // since this is done in a compiler plugin, when running lint we don't have a way to
-            // understand this better, so we just check to see if the name looks like it is a node.
-            if (parentExpression.isLayoutNodeInvocation) return
-
-            // Find the index of the corresponding parameter in the source declaration, that
-            // matches this lambda expression's invocation
-            val parameterIndex = parentExpression.valueArguments.indexOf(node)
-
-            // Parent expression source declaration
-            val parentDeclaration = parentExpression.resolveToUElement() as? UMethod ?: return
-
-            val expectedComposable = when (val source = parentDeclaration.sourcePsi) {
-                // The source is in Kotlin source, so check the parameter for @Composable
-                is KtCallableDeclaration -> {
-                    // Currently type annotations don't appear on the psiType, so we have to look
-                    // through the type reference (https://youtrack.jetbrains.com/issue/KT-45244)
-                    val typeReference = source.valueParameters[parameterIndex]!!
-                        .typeReference as KtTypeReference
-                    typeReference.annotationEntries.any {
-                        (it.toUElement() as UAnnotation).qualifiedName == ComposableFqn
-                    }
-                }
-                // If we cannot resolve the parent expression as a KtCallableDeclaration, then
-                // the source is Java source, or in a class file. We ignore Java source, and
-                // currently there is no way to see the @Composable annotation in the class file
-                // (https://youtrack.jetbrains.com/issue/KT-45244). Instead we can look for the
-                // presence of a `Composer` parameter, as this is added by the Compose compiler
-                // to every Composable function / lambda.
-                else -> {
-                    parentDeclaration.uastParameters[parameterIndex].type.canonicalText
-                        .contains(ComposerFqn)
-                }
-            }
+            val expectedComposable = node.isComposable
 
             // Hack to get the psi of the lambda declaration / source. The !!s here probably
             // aren't safe, but nothing fails with them currently - so it could be a useful
             // indicator if something breaks in the future to let us know to update this lint check.
-            val resolvedLambda = expression.sourcePsi.calleeExpression!!.toUElement()!!.tryResolve()
-                .toUElement()!!.sourcePsi!!
+            val resolvedLambdaSource = expression.sourcePsi.calleeExpression!!.toUElement()!!
+                .tryResolveUDeclaration()!!.sourcePsi!!.toUElement()
 
-            // Unfortunately as Composability isn't carried through UAST, and there are many types
-            // of declarations (types such as foo: @Composable () -> Unit, properties such as val
-            // foo = @Composable {}) the best way to cover this is just check if we contain this
-            // annotation in text. Definitely not ideal, but it should cover most cases so it is
-            // the simplest way for now. Note in particular this will return true for (rare) cases
-            // like (@Composable () -> Unit) -> Unit, so this might need to be updated in the
-            // future if this becomes a common problem.
-            val isComposable = resolvedLambda.text.contains("@Composable")
+            val isComposable = when (resolvedLambdaSource) {
+                is UVariable -> resolvedLambdaSource.isComposable
+                else -> throw IllegalStateException(resolvedLambdaSource.toString())
+            }
 
             if (isComposable != expectedComposable) return
 
@@ -192,10 +148,6 @@
     }
 
     companion object {
-        private val KotlinUFunctionCallExpression.isLayoutNodeInvocation
-            get() = (sourcePsi as? KtCallExpression)?.referenceExpression()?.text
-                ?.endsWith("Node") == true
-
         private const val NonExistentClass = "error.NonExistentClass"
 
         private const val explanation =
@@ -216,6 +168,3 @@
         )
     }
 }
-
-private const val ComposableFqn = "androidx.compose.runtime.Composable"
-private const val ComposerFqn = "androidx.compose.runtime.Composer"
diff --git a/compose/internal-lint-checks/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry b/compose/lint/internal-lint-checks/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
similarity index 100%
rename from compose/internal-lint-checks/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
rename to compose/lint/internal-lint-checks/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
diff --git a/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ListIteratorDetectorTest.kt b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ListIteratorDetectorTest.kt
new file mode 100644
index 0000000..c816721
--- /dev/null
+++ b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ListIteratorDetectorTest.kt
@@ -0,0 +1,204 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.compose.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/* ktlint-disable max-line-length */
+@RunWith(JUnit4::class)
+class ListIteratorDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = ListIteratorDetector()
+
+    override fun getIssues(): MutableList<Issue> = mutableListOf(
+        ListIteratorDetector.ISSUE
+    )
+
+    // These come from class files, so there is a notable difference vs defining them in source
+    // in terms of our parsing.
+    @Test
+    fun stdlibIterableExtensions_calledOnList() {
+        lint().files(
+            kotlin(
+                """
+                package test
+
+                val list = listOf(1, 2, 3)
+
+                fun test() {
+                    list.forEach {  }
+                    list.forEachIndexed { _,_ -> }
+                    list.map { }
+                    list.mapIndexed { _,_ -> }
+                }
+            """
+            )
+        )
+            .run()
+            .expect(
+                """
+src/test/test.kt:7: Error: Creating an unnecessary Iterator to iterate through a List [ListIterator]
+                    list.forEach {  }
+                         ~~~~~~~
+src/test/test.kt:8: Error: Creating an unnecessary Iterator to iterate through a List [ListIterator]
+                    list.forEachIndexed { _,_ -> }
+                         ~~~~~~~~~~~~~~
+src/test/test.kt:9: Error: Creating an unnecessary Iterator to iterate through a List [ListIterator]
+                    list.map { }
+                         ~~~
+src/test/test.kt:10: Error: Creating an unnecessary Iterator to iterate through a List [ListIterator]
+                    list.mapIndexed { _,_ -> }
+                         ~~~~~~~~~~
+4 errors, 0 warnings
+            """
+            )
+    }
+
+    @Test
+    fun userDefinedExtensions_calledOnList() {
+        lint().files(
+            kotlin(
+                """
+                package test
+
+                val list = listOf(1, 2, 3)
+
+                fun test() {
+                    list.fancyForEach {  }
+                    list.fancyDoSomething()
+                }
+
+                inline fun <T> Iterable<T>.fancyForEach(action: (T) -> Unit): Unit {}
+
+                fun Iterable<*>.fancyDoSomething(): Boolean = true
+            """
+            )
+        )
+            .run()
+            .expect(
+                """
+src/test/test.kt:7: Error: Creating an unnecessary Iterator to iterate through a List [ListIterator]
+                    list.fancyForEach {  }
+                         ~~~~~~~~~~~~
+src/test/test.kt:8: Error: Creating an unnecessary Iterator to iterate through a List [ListIterator]
+                    list.fancyDoSomething()
+                         ~~~~~~~~~~~~~~~~
+2 errors, 0 warnings
+            """
+            )
+    }
+
+    // These come from class files, so there is a notable difference vs defining them in source
+    // in terms of our parsing.
+    @Test
+    fun stdlibIterableExtensions_calledOnNonList() {
+        lint().files(
+            kotlin(
+                """
+                package test
+
+                val set = setOf(1, 2, 3)
+
+                fun test() {
+                    set.forEach {  }
+                    set.forEachIndexed { _,_ -> }
+                    set.map { }
+                    set.mapIndexed { _,_ -> }
+                }
+            """
+            )
+        )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun userDefinedExtensions_calledOnNonList() {
+        lint().files(
+            kotlin(
+                """
+                package test
+
+                val set = setOf(1, 2, 3)
+
+                fun test() {
+                    set.fancyForEach {  }
+                    set.fancyDoSomething()
+                }
+
+                inline fun <T> Iterable<T>.fancyForEach(action: (T) -> Unit): Unit {}
+
+                fun Iterable<*>.fancyDoSomething(): Boolean = true
+            """
+            )
+        )
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun inOperatorCalledOnList() {
+        lint().files(
+            kotlin(
+                """
+                package test
+
+                val list = listOf(1, 2, 3)
+
+                fun test() {
+                    for (e in list) { }
+                }
+            """
+            )
+        )
+            .run()
+            .expect(
+                """
+src/test/test.kt:7: Error: Creating an unnecessary Iterator to iterate through a List [ListIterator]
+                    for (e in list) { }
+                           ~~
+1 errors, 0 warnings
+            """
+            )
+    }
+
+    @Test
+    fun inOperatorCalledOnNonList() {
+        lint().files(
+            kotlin(
+                """
+                val list = listOf(1, 2, 3)
+                val set = setOf(1, 2, 3)
+
+                fun test() {
+                    for (i in list.indices) { }
+                    for (e in set) { }
+                }
+            """
+            )
+        )
+            .run()
+            .expectClean()
+    }
+}
+/* ktlint-enable max-line-length */
diff --git a/compose/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt
similarity index 93%
rename from compose/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt
rename to compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt
index 5e95ed8..23e2a69 100644
--- a/compose/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt
+++ b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ModifierInspectorInfoDetectorTest.kt
@@ -32,7 +32,7 @@
 
     override fun getIssues(): List<Issue> = listOf(ModifierInspectorInfoDetector.ISSUE)
 
-    private val inspectableInfoFile = kotlin(
+    private val inspectableInfoStub = kotlin(
         """
         package androidx.compose.ui.platform
 
@@ -98,30 +98,13 @@
         """
     ).indented()
 
-    private val modifierFile = kotlin(
+    private val composedStub = kotlin(
         """
         package androidx.compose.ui
 
         import androidx.compose.ui.platform.InspectorInfo
         import androidx.compose.ui.platform.InspectorValueInfo
 
-        interface Modifier {
-          infix fun then(other: Modifier): Modifier =
-              if (other === Modifier) this else CombinedModifier(this, other)
-
-          interface Element : Modifier {
-          }
-
-          companion object : Modifier {
-            override infix fun then(other: Modifier): Modifier = other
-          }
-        }
-
-        class CombinedModifier(
-            private val outer: Modifier,
-            private val inner: Modifier
-        ) : Modifier {}
-
         fun Modifier.composed(
             inspectorInfo: InspectorInfo.() -> Unit = NoInspectorInfo,
             factory: Modifier.() -> Modifier
@@ -131,26 +114,15 @@
             inspectorInfo: InspectorInfo.() -> Unit,
             val factory: Modifier.() -> Modifier
         ) : Modifier.Element, InspectorValueInfo(inspectorInfo)
-
-        """
-    ).indented()
-
-    private val rememberFile = kotlin(
-        """
-        package androidx.compose.runtime
-
-        fun <T> remember(calculation: () -> T): T = calculation()
-
-        class Remember
-
         """
     ).indented()
 
     @Test
     fun existingInspectorInfo() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -183,8 +155,9 @@
     @Test
     fun existingInspectorInfoWithStatementsBeforeDefinition() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -232,8 +205,9 @@
     @Test
     fun existingInspectorInfoWithValue() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -265,8 +239,9 @@
     @Test
     fun existingInspectorInfoViaSynonym() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -308,8 +283,9 @@
     @Test
     fun existingInspectorInfoWithAnonymousClass() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -331,8 +307,9 @@
     @Test
     fun existingInspectorInfoWithDataClassMemberValues() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -398,8 +375,9 @@
     @Test
     fun existingInspectorInfoWithConditional() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -447,8 +425,9 @@
     @Test
     fun existingInspectorInfoWithWhen() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -487,8 +466,9 @@
     @Test
     fun existingInspectorInfoWithConditionals() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -545,8 +525,9 @@
     @Test
     fun composedModifierWithInspectorInfo() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -585,9 +566,10 @@
     @Test
     fun rememberModifierInfo() {
         lint().files(
-            modifierFile,
-            rememberFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            kotlin(Stubs.Remember),
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -622,7 +604,8 @@
     @Test
     fun emptyModifier() {
         lint().files(
-            modifierFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -641,8 +624,9 @@
     @Test
     fun acceptMissingInspectorInfoInSamples() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui.demos.whatever
@@ -668,8 +652,9 @@
     @Test
     fun missingInspectorInfo() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -702,8 +687,9 @@
     @Test
     fun composedModifierWithMissingInspectorInfo() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -736,8 +722,9 @@
     @Test
     fun missingInspectorInfoFromInnerClassImplementation() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -775,8 +762,9 @@
     @Test
     fun inspectorInfoWithWrongName() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -815,8 +803,9 @@
     @Test
     fun inspectorInfoWithWrongValue() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -855,8 +844,9 @@
     @Test
     fun inspectorInfoWithWrongValueWhenMultipleAreAvailable() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -895,8 +885,9 @@
     @Test
     fun inspectorInfoWithWrongParameterNameInProperties() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -936,8 +927,9 @@
     @Test
     fun inspectorInfoWithMismatchInProperties() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -976,8 +968,9 @@
     @Test
     fun inspectorInfoWithMissingDebugSelector() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -1017,8 +1010,9 @@
     @Test
     fun inspectorInfoWithMissingName() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -1056,8 +1050,9 @@
     @Test
     fun inspectorInfoWithMissingVariables() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -1102,8 +1097,9 @@
     @Test
     fun inspectorInfoWithMissingDataClassMemberValues() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
@@ -1147,8 +1143,9 @@
     @Test
     fun missingInfoInConditionals() {
         lint().files(
-            modifierFile,
-            inspectableInfoFile,
+            kotlin(Stubs.Modifier),
+            composedStub,
+            inspectableInfoStub,
             kotlin(
                 """
                 package androidx.compose.ui
diff --git a/compose/internal-lint-checks/src/test/java/androidx/compose/lint/UnnecessaryLambdaCreationDetectorTest.kt b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/UnnecessaryLambdaCreationDetectorTest.kt
similarity index 91%
rename from compose/internal-lint-checks/src/test/java/androidx/compose/lint/UnnecessaryLambdaCreationDetectorTest.kt
rename to compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/UnnecessaryLambdaCreationDetectorTest.kt
index 12a1f87..bbe37b1 100644
--- a/compose/internal-lint-checks/src/test/java/androidx/compose/lint/UnnecessaryLambdaCreationDetectorTest.kt
+++ b/compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/UnnecessaryLambdaCreationDetectorTest.kt
@@ -25,28 +25,13 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 import androidx.compose.lint.UnnecessaryLambdaCreationDetector.Companion.ISSUE
+import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
 import org.intellij.lang.annotations.Language
 
 /* ktlint-disable max-line-length */
 @RunWith(JUnit4::class)
 class UnnecessaryLambdaCreationDetectorTest {
 
-    private val composableStub = kt(
-        """
-        package androidx.compose.runtime
-
-        @MustBeDocumented
-        @Retention(AnnotationRetention.BINARY)
-        @Target(
-            AnnotationTarget.FUNCTION,
-            AnnotationTarget.TYPE,
-            AnnotationTarget.TYPE_PARAMETER,
-            AnnotationTarget.PROPERTY
-        )
-        annotation class Composable
-    """
-    )
-
     private val stub = kt(
         """
         package test
@@ -68,7 +53,7 @@
 
     private fun check(@Language("kotlin") code: String): TestLintResult {
         return TestLintTask.lint()
-            .files(kt(code.trimIndent()), stub, composableStub)
+            .files(kt(code.trimIndent()), stub, kotlin(Stubs.Composable))
             .allowMissingSdk(true)
             .issues(ISSUE)
             .run()
@@ -223,14 +208,7 @@
                 }
             }
         """
-        ).expect(
-            """
-src/test/test.kt:23: Error: Creating an unnecessary lambda to emit a captured lambda [UnnecessaryLambdaCreation]
-        parameterizedLambda(child)
-        ~~~~~~~~~~~~~~~~~~~
-1 errors, 0 warnings
-        """
-        )
+        ).expectClean()
     }
 
     @Test
diff --git a/compose/material/material-icons-core/build.gradle b/compose/material/material-icons-core/build.gradle
index ad390e0..4ef400b 100644
--- a/compose/material/material-icons-core/build.gradle
+++ b/compose/material/material-icons-core/build.gradle
@@ -17,10 +17,8 @@
 
 import androidx.build.AndroidXUiPlugin
 import androidx.build.LibraryGroups
-import androidx.build.LibraryVersions
-import androidx.build.Publish
+import androidx.build.LibraryType
 import androidx.compose.material.icons.generator.tasks.IconGenerationTask
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 import static androidx.build.dependencies.DependenciesKt.*
 
@@ -75,7 +73,7 @@
 
 androidx {
     name = "Compose Material Icons Core"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.MATERIAL
     inceptionYear = "2020"
     description = "Compose Material Design core icons. This module contains the most commonly used set of Material icons."
diff --git a/compose/material/material-icons-core/lint-baseline.xml b/compose/material/material-icons-core/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/material/material-icons-core/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/material/material-icons-core/samples/lint-baseline.xml b/compose/material/material-icons-core/samples/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/material/material-icons-core/samples/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/material/material-icons-extended/build.gradle b/compose/material/material-icons-extended/build.gradle
index 8808af5..eefd74d 100644
--- a/compose/material/material-icons-extended/build.gradle
+++ b/compose/material/material-icons-extended/build.gradle
@@ -16,7 +16,7 @@
 
 import androidx.build.AndroidXUiPlugin
 import androidx.build.LibraryGroups
-import androidx.build.Publish
+import androidx.build.LibraryType
 import androidx.build.RunApiTasks
 import androidx.compose.material.icons.generator.tasks.IconGenerationTask
 
@@ -115,7 +115,7 @@
 
 androidx {
     name = "Compose Material Icons Extended"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.MATERIAL
     // This module has a large number (5000+) of generated source files and so doc generation /
     // API tracking will simply take too long
diff --git a/compose/material/material-lint/build.gradle b/compose/material/material-lint/build.gradle
new file mode 100644
index 0000000..98a389a
--- /dev/null
+++ b/compose/material/material-lint/build.gradle
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.build.BundleInsideHelper
+import androidx.build.LibraryGroups
+import androidx.build.LibraryType
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+    id("AndroidXPlugin")
+    id("kotlin")
+}
+
+BundleInsideHelper.forInsideLintJar(project)
+
+dependencies {
+    // compileOnly because we use lintChecks and it doesn't allow other types of deps
+    // this ugly hack exists because of b/63873667
+    if (rootProject.hasProperty("android.injected.invoked.from.ide")) {
+        compileOnly LINT_API_LATEST
+    } else {
+        compileOnly LINT_API_MIN
+    }
+    compileOnly KOTLIN_STDLIB
+    bundleInside(project(":compose:lint:common"))
+
+    testImplementation KOTLIN_STDLIB
+    testImplementation LINT_CORE
+    testImplementation LINT_TESTS
+}
+
+androidx {
+    name = "Compose Material Lint Checks"
+    type = LibraryType.LINT
+    mavenGroup = LibraryGroups.Compose.MATERIAL
+    inceptionYear = "2021"
+    description = "Compose Material Lint Checks"
+}
diff --git a/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ColorsDetector.kt b/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ColorsDetector.kt
new file mode 100644
index 0000000..27efac9
--- /dev/null
+++ b/compose/material/material-lint/src/main/java/androidx/compose/material/lint/ColorsDetector.kt
@@ -0,0 +1,216 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.compose.material.lint
+
+import androidx.compose.lint.Name
+import androidx.compose.lint.Package
+import androidx.compose.lint.isInPackageName
+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 com.intellij.psi.PsiParameter
+import org.jetbrains.kotlin.asJava.elements.KtLightElement
+import org.jetbrains.kotlin.psi.KtParameter
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.util.isConstructorCall
+import java.util.EnumSet
+
+/**
+ * [Detector] that checks `Colors` definitions for correctness.
+ *
+ * Background colors that share the same color (such as `surface` and `background`) should also
+ * share the same 'on' color (`onSurface` and `onBackground`) - otherwise we can't know which
+ * color to use for a given background color by value.
+ */
+class ColorsDetector : Detector(), SourceCodeScanner {
+    override fun getApplicableUastTypes() = listOf(UCallExpression::class.java)
+
+    override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
+        override fun visitCallExpression(node: UCallExpression) {
+            val method = node.resolve() ?: return
+            if (!method.isInPackageName(MaterialPackageName)) return
+
+            if (node.isConstructorCall()) {
+                if (method.containingClass?.name != Colors.shortName) return
+            } else {
+                // Functions with inline class parameters have their names mangled, so we use
+                // startsWith instead of comparing the full name.
+                if (!method.name.startsWith(LightColors.shortName) &&
+                    !method.name.startsWith(DarkColors.shortName)
+                ) return
+            }
+
+            val parameters = method.parameterList.parameters.mapIndexed { index, parameter ->
+                // UCallExpressionEx is deprecated, but getArgumentForParameter doesn't exist on
+                // UCallExpression on the version of lint we compile against.
+                // TODO: remove when we upgrade the min lint version we compile against b/182832722
+                @Suppress("DEPRECATION")
+                val argumentForParameter = (node as org.jetbrains.uast.UCallExpressionEx)
+                    .getArgumentForParameter(index)
+                ParameterWithArgument(
+                    parameter,
+                    argumentForParameter
+                )
+            }
+
+            // Filter to only background colors, and group by their value
+            val backgroundColorGroups = parameters.filter {
+                it.parameter.name in OnColorMap.keys
+            }.filter {
+                // Filter out any parameters that have unknown defaults / arguments, we can't do
+                // anything here about them
+                it.sourceText != null
+            }.groupBy {
+                it.sourceText
+            }.values
+
+            // For each grouped pair of colors, make sure that all corresponding 'on' colors have
+            // the same color
+            backgroundColorGroups.forEach { colors ->
+                // Find all corresponding onColors for these colors and group them by value
+                val onColorGroups = colors.map { parameter ->
+                    val background = parameter.parameter.name
+                    val onColor = OnColorMap[background]
+                    parameters.first {
+                        it.parameter.name == onColor
+                    }
+                }
+                    // If multiple background colors have the same color (such as `primary` /
+                    // `primaryVariant`) then filter the duplicates out so we don't report the same
+                    // 'on' color multiple times.
+                    .distinctBy { it.parameter.name }
+                    .groupBy {
+                        it.sourceText
+                    }
+
+                // Report if there are multiple groups (i.e different values between 'on' colors)
+                if (onColorGroups.size > 1) {
+                    onColorGroups.values.forEach { group ->
+                        group.forEach { parameter ->
+                            val argument = parameter.argument
+                            // If the conflicting color comes from the default value of a function,
+                            // there is nothing to report - just report the clashing colors that
+                            // the user explicitly provides.
+                            if (argument != null) {
+                                context.report(
+                                    ConflictingOnColor,
+                                    argument,
+                                    context.getNameLocation(argument),
+                                    "Conflicting 'on' color for a given background"
+                                )
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    companion object {
+        val ConflictingOnColor = Issue.create(
+            "ConflictingOnColor",
+            "Background colors with the same value should have the same 'on' color",
+            "In the Material color system background colors have a corresponding 'on' " +
+                "color which is used for the content color inside a component. For example, a " +
+                "button colored `primary` will have `onPrimary` text. Because of this, it is " +
+                "important that there is only one possible `onColor` for a given color value, " +
+                "otherwise there is no way to know which 'on' color should be used inside a " +
+                "component. To fix this either use the same 'on' color for identical background " +
+                "colors, or use a different background color for each 'on' color.",
+            Category.CORRECTNESS, 3, Severity.ERROR,
+            Implementation(
+                ColorsDetector::class.java,
+                EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
+            )
+        )
+    }
+}
+
+/**
+ * Represents a [parameter] with the corresponding [argument] provided as the value for the
+ * parameter. If [parameter] has a default value, [argument] may be null.
+ */
+class ParameterWithArgument(
+    val parameter: PsiParameter,
+    val argument: UExpression?
+) {
+    /**
+     * String representing the text passed as the argument / provided within the default.
+     *
+     * Note: this will fail in rare cases where the same underlying value is referenced to in a
+     * different way, such as:
+     *
+     * ```
+     * val white = Color.White
+     * ...
+     * onPrimary = white,
+     * onSecondary = Color.White
+     * ```
+     *
+     * Theoretically we can resolve declarations, but this would require a lot of work to handle
+     * different types of references, such as parameters and properties, and still will miss some
+     * cases such as when this is defined inside a function with an external parameter that we
+     * can't resolve.
+     *
+     * This string is `null` if no argument was provided, and a default value exists in a class
+     * file - so we can't resolve what it is.
+     */
+    val sourceText: String? by lazy {
+        val argumentText = argument?.sourcePsi?.text
+        when {
+            // An argument was provided
+            argumentText != null -> argumentText
+            // A default value exists (so !! is safe), and we are browsing Kotlin source
+            // Note: this should be is KtLightParameter, but this was changed from an interface
+            // to a class, so we get an IncompatibleClassChangeError.
+            // TODO: change to KtParameter when we upgrade the min lint version we compile against
+            //  b/182832722
+            parameter is KtLightElement<*, *> -> {
+                (parameter.kotlinOrigin!! as KtParameter).defaultValue!!.text
+            }
+            // A default value exists, but it is in a class file so we can't access it anymore
+            else -> null
+        }
+    }
+}
+
+/**
+ * Map of background colors to corresponding 'on' colors.
+ */
+private val OnColorMap = mapOf(
+    "primary" to "onPrimary",
+    "primaryVariant" to "onPrimary",
+    "secondary" to "onSecondary",
+    "secondaryVariant" to "onSecondary",
+    "background" to "onBackground",
+    "surface" to "onSurface",
+    "error" to "onError"
+)
+
+private val MaterialPackageName = Package("androidx.compose.material")
+private val LightColors = Name(MaterialPackageName, "lightColors")
+private val DarkColors = Name(MaterialPackageName, "darkColors")
+private val Colors = Name(MaterialPackageName, "Colors")
diff --git a/compose/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt b/compose/material/material-lint/src/main/java/androidx/compose/material/lint/MaterialIssueRegistry.kt
similarity index 62%
copy from compose/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
copy to compose/material/material-lint/src/main/java/androidx/compose/material/lint/MaterialIssueRegistry.kt
index a5803b3..76b3d50 100644
--- a/compose/internal-lint-checks/src/main/java/androidx/compose/lint/ComposeIssueRegistry.kt
+++ b/compose/material/material-lint/src/main/java/androidx/compose/material/lint/MaterialIssueRegistry.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019 The Android Open Source Project
+ * Copyright 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,20 +14,19 @@
  * limitations under the License.
  */
 
-package androidx.compose.lint
+package androidx.compose.material.lint
 
-import androidx.build.lint.AndroidXIssueRegistry
 import com.android.tools.lint.client.api.IssueRegistry
 import com.android.tools.lint.detector.api.CURRENT_API
-import com.android.tools.lint.detector.api.Issue
 
-class ComposeIssueRegistry : IssueRegistry() {
-    override val minApi = CURRENT_API
+/**
+ * [IssueRegistry] containing runtime specific lint issues.
+ */
+class MaterialIssueRegistry : IssueRegistry() {
+    // Tests are run with this version. We ensure that with ApiLintVersionsTest
     override val api = 8
-    override val issues get(): List<Issue> {
-        return listOf(
-            ModifierInspectorInfoDetector.ISSUE,
-            UnnecessaryLambdaCreationDetector.ISSUE,
-        ) + AndroidXIssueRegistry.Issues
-    }
+    override val minApi = CURRENT_API
+    override val issues get() = listOf(
+        ColorsDetector.ConflictingOnColor
+    )
 }
diff --git a/compose/material/material-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry b/compose/material/material-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
new file mode 100644
index 0000000..38f0439
--- /dev/null
+++ b/compose/material/material-lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
@@ -0,0 +1 @@
+androidx.compose.material.lint.MaterialIssueRegistry
diff --git a/compose/material/material-lint/src/test/java/androidx/compose/material/lint/ApiLintVersionsTest.kt b/compose/material/material-lint/src/test/java/androidx/compose/material/lint/ApiLintVersionsTest.kt
new file mode 100644
index 0000000..0364538
--- /dev/null
+++ b/compose/material/material-lint/src/test/java/androidx/compose/material/lint/ApiLintVersionsTest.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.compose.material.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 = MaterialIssueRegistry()
+        // we hardcode version registry.api to the version that is used to run tests
+        assertThat(registry.api).isEqualTo(CURRENT_API)
+        // Intentionally fails in IDE, because we use different API version in
+        // studio and command line
+        assertThat(registry.minApi).isEqualTo(3)
+    }
+}
diff --git a/compose/material/material-lint/src/test/java/androidx/compose/material/lint/ColorsDetectorTest.kt b/compose/material/material-lint/src/test/java/androidx/compose/material/lint/ColorsDetectorTest.kt
new file mode 100644
index 0000000..27e545d
--- /dev/null
+++ b/compose/material/material-lint/src/test/java/androidx/compose/material/lint/ColorsDetectorTest.kt
@@ -0,0 +1,409 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.compose.material.lint
+
+import androidx.compose.lint.Stubs
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/* ktlint-disable max-line-length */
+@RunWith(JUnit4::class)
+
+// TODO: add tests for methods defined in class files when we update Lint to support bytecode()
+//  test files
+
+/**
+ * Test for [ColorsDetector].
+ */
+class ColorsDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = ColorsDetector()
+
+    override fun getIssues(): MutableList<Issue> = mutableListOf(ColorsDetector.ConflictingOnColor)
+
+    // Simplified Color.kt stubs
+    private val ColorStub = kotlin(Stubs.Color)
+
+    // Simplified Colors.kt stubs
+    private val ColorsStub = kotlin(
+        """
+            package androidx.compose.material
+
+            import androidx.compose.ui.graphics.Color
+
+            class Colors(
+                primary: Color,
+                primaryVariant: Color,
+                secondary: Color,
+                secondaryVariant: Color,
+                background: Color,
+                surface: Color,
+                error: Color,
+                onPrimary: Color,
+                onSecondary: Color,
+                onBackground: Color,
+                onSurface: Color,
+                onError: Color,
+                isLight: Boolean
+            )
+
+            fun lightColors(
+                primary: Color = Color(0xFF6200EE),
+                primaryVariant: Color = Color(0xFF3700B3),
+                secondary: Color = Color(0xFF03DAC6),
+                secondaryVariant: Color = Color(0xFF018786),
+                background: Color = Color.White,
+                surface: Color = Color.White,
+                error: Color = Color(0xFFB00020),
+                onPrimary: Color = Color.White,
+                onSecondary: Color = Color.Black,
+                onBackground: Color = Color.Black,
+                onSurface: Color = Color.Black,
+                onError: Color = Color.White
+            ): Colors = Colors(
+                primary,
+                primaryVariant,
+                secondary,
+                secondaryVariant,
+                background,
+                surface,
+                error,
+                onPrimary,
+                onSecondary,
+                onBackground,
+                onSurface,
+                onError,
+                true
+            )
+
+            fun darkColors(
+                primary: Color = Color(0xFFBB86FC),
+                primaryVariant: Color = Color(0xFF3700B3),
+                secondary: Color = Color(0xFF03DAC6),
+                secondaryVariant: Color = secondary,
+                background: Color = Color(0xFF121212),
+                surface: Color = Color(0xFF121212),
+                error: Color = Color(0xFFCF6679),
+                onPrimary: Color = Color.Black,
+                onSecondary: Color = Color.Black,
+                onBackground: Color = Color.White,
+                onSurface: Color = Color.White,
+                onError: Color = Color.Black
+            ): Colors = Colors(
+                primary,
+                primaryVariant,
+                secondary,
+                secondaryVariant,
+                background,
+                surface,
+                error,
+                onPrimary,
+                onSecondary,
+                onBackground,
+                onSurface,
+                onError,
+                false
+            )
+        """
+    )
+
+    @Test
+    fun constructorErrors() {
+        lint().files(
+            kotlin(
+                """
+                package androidx.compose.material.foo
+
+                import androidx.compose.material.*
+                import androidx.compose.ui.graphics.*
+
+                val colors = Colors(
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    Color.Red,
+                    false
+                )
+
+                val colors2 = Colors(
+                    primary = Color.White,
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    background = Color.Blue,
+                    Color.White,
+                    Color.Green,
+                    Color.White,
+                    Color.Blue,
+                    onBackground = Color.White,
+                    onSurface = Color.White,
+                    onError = Color.Red,
+                    isLight = false
+                )
+
+                val yellow200 = Color(0xffffeb46)
+                val yellow400 = Color(0xffffc000)
+                val yellow500 = Color(0xffffde03)
+
+                val colors3 = Colors(
+                    yellow200,
+                    yellow400,
+                    yellow200,
+                    secondaryVariant = yellow200,
+                    Color.White,
+                    surface = Color.Blue,
+                    Color.White,
+                    Color.White,
+                    yellow400,
+                    Color.Blue,
+                    onSurface = Color(0xFFFFBBCC),
+                    yellow500,
+                    false
+                )
+            """
+            ),
+            ColorStub,
+            ColorsStub
+        )
+            .run()
+            .expect(
+                """
+src/androidx/compose/material/foo/test.kt:15: Error: Conflicting 'on' color for a given background [ConflictingOnColor]
+                    Color.White,
+                    ~~~~~~~~~~~
+src/androidx/compose/material/foo/test.kt:16: Error: Conflicting 'on' color for a given background [ConflictingOnColor]
+                    Color.White,
+                    ~~~~~~~~~~~
+src/androidx/compose/material/foo/test.kt:17: Error: Conflicting 'on' color for a given background [ConflictingOnColor]
+                    Color.White,
+                    ~~~~~~~~~~~
+src/androidx/compose/material/foo/test.kt:18: Error: Conflicting 'on' color for a given background [ConflictingOnColor]
+                    Color.White,
+                    ~~~~~~~~~~~
+src/androidx/compose/material/foo/test.kt:19: Error: Conflicting 'on' color for a given background [ConflictingOnColor]
+                    Color.Red,
+                    ~~~~~~~~~
+src/androidx/compose/material/foo/test.kt:31: Error: Conflicting 'on' color for a given background [ConflictingOnColor]
+                    Color.White,
+                    ~~~~~~~~~~~
+src/androidx/compose/material/foo/test.kt:32: Error: Conflicting 'on' color for a given background [ConflictingOnColor]
+                    Color.Blue,
+                    ~~~~~~~~~~
+src/androidx/compose/material/foo/test.kt:34: Error: Conflicting 'on' color for a given background [ConflictingOnColor]
+                    onSurface = Color.White,
+                                ~~~~~~~~~~~
+src/androidx/compose/material/foo/test.kt:51: Error: Conflicting 'on' color for a given background [ConflictingOnColor]
+                    Color.White,
+                    ~~~~~~~~~~~
+src/androidx/compose/material/foo/test.kt:52: Error: Conflicting 'on' color for a given background [ConflictingOnColor]
+                    yellow400,
+                    ~~~~~~~~~
+src/androidx/compose/material/foo/test.kt:53: Error: Conflicting 'on' color for a given background [ConflictingOnColor]
+                    Color.Blue,
+                    ~~~~~~~~~~
+src/androidx/compose/material/foo/test.kt:55: Error: Conflicting 'on' color for a given background [ConflictingOnColor]
+                    yellow500,
+                    ~~~~~~~~~
+12 errors, 0 warnings
+            """
+            )
+    }
+
+    @Test
+    fun lightColorsErrors() {
+        lint().files(
+            kotlin(
+                """
+                package androidx.compose.material.foo
+
+                import androidx.compose.material.*
+                import androidx.compose.ui.graphics.*
+
+                val colors = lightColors(
+                    // Color.White is used by default for some colors, so onPrimary should conflict
+                    primary = Color.White,
+                    onPrimary = Color.Red,
+                )
+
+                val yellow200 = Color(0xffffeb46)
+                val yellow400 = Color(0xffffc000)
+                val yellow500 = Color(0xffffde03)
+
+                val colors2 = lightColors(
+                    primary = yellow200,
+                    background = yellow200,
+                    onPrimary = yellow400,
+                    onBackground = Color.Green,
+                )
+            """
+            ),
+            ColorStub,
+            ColorsStub
+        )
+            .run()
+            .expect(
+                """
+src/androidx/compose/material/foo/test.kt:10: Error: Conflicting 'on' color for a given background [ConflictingOnColor]
+                    onPrimary = Color.Red,
+                                ~~~~~~~~~
+src/androidx/compose/material/foo/test.kt:20: Error: Conflicting 'on' color for a given background [ConflictingOnColor]
+                    onPrimary = yellow400,
+                                ~~~~~~~~~
+src/androidx/compose/material/foo/test.kt:21: Error: Conflicting 'on' color for a given background [ConflictingOnColor]
+                    onBackground = Color.Green,
+                                   ~~~~~~~~~~~
+3 errors, 0 warnings
+            """
+            )
+    }
+
+    @Test
+    fun darkColorsErrors() {
+        lint().files(
+            kotlin(
+                """
+                package androidx.compose.material.foo
+
+                import androidx.compose.material.*
+                import androidx.compose.ui.graphics.*
+
+                val colors = darkColors(
+                    // Color(0xFF121212) is used by default for some colors, so onPrimary should
+                    // conflict
+                    primary = Color(0xFF121212),
+                    onPrimary = Color.Red,
+                )
+
+                val yellow200 = Color(0xffffeb46)
+                val yellow400 = Color(0xffffc000)
+                val yellow500 = Color(0xffffde03)
+
+                val colors2 = darkColors(
+                    primary = yellow200,
+                    background = yellow200,
+                    onPrimary = yellow400,
+                    onBackground = Color.Green,
+                )
+            """
+            ),
+            ColorStub,
+            ColorsStub
+        )
+            .run()
+            .expect(
+                """
+src/androidx/compose/material/foo/test.kt:11: Error: Conflicting 'on' color for a given background [ConflictingOnColor]
+                    onPrimary = Color.Red,
+                                ~~~~~~~~~
+src/androidx/compose/material/foo/test.kt:21: Error: Conflicting 'on' color for a given background [ConflictingOnColor]
+                    onPrimary = yellow400,
+                                ~~~~~~~~~
+src/androidx/compose/material/foo/test.kt:22: Error: Conflicting 'on' color for a given background [ConflictingOnColor]
+                    onBackground = Color.Green,
+                                   ~~~~~~~~~~~
+3 errors, 0 warnings
+            """
+            )
+    }
+
+    @Test
+    fun noErrors() {
+        lint().files(
+            kotlin(
+                """
+                package androidx.compose.material.foo
+
+                import androidx.compose.material.*
+                import androidx.compose.ui.graphics.*
+
+                val colors = lightColors()
+                val colors2 = darkColors()
+                val colors3 = Colors(
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    false
+                )
+
+                val yellow200 = Color(0xffffeb46)
+                val yellow400 = Color(0xffffc000)
+                val yellow500 = Color(0xffffde03)
+
+                val colors4 = Colors(
+                    yellow200,
+                    yellow400,
+                    Color.White,
+                    secondaryVariant = yellow500,
+                    Color.White,
+                    surface = Color.Blue,
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    Color.White,
+                    onSurface = Color(0xFFFFBBCC),
+                    Color.White,
+                    false
+                )
+
+                val colors5 = lightColors(
+                    yellow200,
+                    yellow400,
+                    Color.White,
+                    surface = Color.Blue,
+                    onSurface = Color(0xFFFFBBCC),
+                )
+
+                val colors6 = darkColors(
+                    yellow200,
+                    yellow400,
+                    Color.White,
+                    surface = Color.Blue,
+                    onSurface = Color(0xFFFFBBCC),
+                )
+
+            """
+            ),
+            ColorStub,
+            ColorsStub
+        )
+            .run()
+            .expectClean()
+    }
+}
+/* ktlint-enable max-line-length */
diff --git a/compose/material/material-ripple/build.gradle b/compose/material/material-ripple/build.gradle
index e2cede2..4529ad0 100644
--- a/compose/material/material-ripple/build.gradle
+++ b/compose/material/material-ripple/build.gradle
@@ -16,8 +16,7 @@
 
 import androidx.build.AndroidXUiPlugin
 import androidx.build.LibraryGroups
-import androidx.build.Publish
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import androidx.build.LibraryType
 
 import static androidx.build.dependencies.DependenciesKt.*
 
@@ -86,7 +85,7 @@
 
 androidx {
     name = "Compose Material Ripple"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.MATERIAL
     inceptionYear = "2020"
     description = "Material ripple used to build interactive components"
diff --git a/compose/material/material-ripple/lint-baseline.xml b/compose/material/material-ripple/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/material/material-ripple/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/Ripple.kt b/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/Ripple.kt
index 09ad441..466ead1 100644
--- a/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/Ripple.kt
+++ b/compose/material/material-ripple/src/commonMain/kotlin/androidx/compose/material/ripple/Ripple.kt
@@ -41,6 +41,7 @@
 import androidx.compose.ui.graphics.isSpecified
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.isUnspecified
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.launch
 
@@ -126,9 +127,7 @@
             interactionSource.interactions.collect { interaction ->
                 when (interaction) {
                     is PressInteraction.Press -> {
-                        launch {
-                            instance.addRipple(interaction)
-                        }
+                        instance.addRipple(interaction, this)
                     }
                     is PressInteraction.Release -> {
                         instance.removeRipple(interaction.press)
@@ -136,7 +135,7 @@
                     is PressInteraction.Cancel -> {
                         instance.removeRipple(interaction.press)
                     }
-                    else -> instance.updateStateLayer(interaction)
+                    else -> instance.updateStateLayer(interaction, this)
                 }
             }
         }
@@ -184,7 +183,7 @@
         drawRipples(color)
     }
 
-    suspend fun addRipple(interaction: PressInteraction.Press) {
+    fun addRipple(interaction: PressInteraction.Press, scope: CoroutineScope) {
         // Finish existing ripples
         ripples.forEach { (_, ripple) -> ripple.finish() }
         val origin = if (bounded) interaction.pressPosition else null
@@ -194,12 +193,17 @@
             bounded = bounded
         )
         ripples[interaction] = rippleAnimation
-        rippleAnimation.animate()
-        ripples.remove(interaction)
+        scope.launch {
+            try {
+                rippleAnimation.animate()
+            } finally {
+                ripples.remove(interaction)
+            }
+        }
     }
 
-    suspend fun updateStateLayer(interaction: Interaction) {
-        stateLayer.handleInteraction(interaction)
+    fun updateStateLayer(interaction: Interaction, scope: CoroutineScope) {
+        stateLayer.handleInteraction(interaction, scope)
     }
 
     fun removeRipple(interaction: PressInteraction.Press) {
@@ -262,7 +266,7 @@
     private val interactions: MutableList<Interaction> = mutableListOf()
     private var currentInteraction: Interaction? = null
 
-    suspend fun handleInteraction(interaction: Interaction) {
+    fun handleInteraction(interaction: Interaction, scope: CoroutineScope) {
         // TODO: handle hover / focus states
         when (interaction) {
             is DragInteraction.Start -> {
@@ -288,11 +292,15 @@
                 }
                 val incomingAnimationSpec = incomingStateLayerAnimationSpecFor(newInteraction)
 
-                animatedAlpha.animateTo(targetAlpha, incomingAnimationSpec)
+                scope.launch {
+                    animatedAlpha.animateTo(targetAlpha, incomingAnimationSpec)
+                }
             } else {
                 val outgoingAnimationSpec = outgoingStateLayerAnimationSpecFor(currentInteraction)
 
-                animatedAlpha.animateTo(0f, outgoingAnimationSpec)
+                scope.launch {
+                    animatedAlpha.animateTo(0f, outgoingAnimationSpec)
+                }
             }
             currentInteraction = newInteraction
         }
diff --git a/compose/material/material/build.gradle b/compose/material/material/build.gradle
index d8093ae..dbc14ae 100644
--- a/compose/material/material/build.gradle
+++ b/compose/material/material/build.gradle
@@ -16,8 +16,7 @@
 
 import androidx.build.AndroidXUiPlugin
 import androidx.build.LibraryGroups
-import androidx.build.Publish
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import androidx.build.LibraryType
 
 import static androidx.build.dependencies.DependenciesKt.*
 
@@ -67,6 +66,8 @@
         androidTestImplementation(MOCKITO_KOTLIN, {
             exclude group: "org.mockito" // to keep control on the mockito version
         })
+
+        lintPublish project(":compose:material:material-lint")
     }
 }
 
@@ -131,7 +132,7 @@
 
 androidx {
     name = "Compose Material Components"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.MATERIAL
     inceptionYear = "2018"
     description = "Compose Material Design Components library"
diff --git a/compose/material/material/icons/generator/lint-baseline.xml b/compose/material/material/icons/generator/lint-baseline.xml
deleted file mode 100644
index 297ae16..0000000
--- a/compose/material/material/icons/generator/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" version="4.2.0-beta02">
-
-</issues>
diff --git a/compose/material/material/integration-tests/material-demos/lint-baseline.xml b/compose/material/material/integration-tests/material-demos/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/material/material/integration-tests/material-demos/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/BottomNavigationDemo.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/BottomNavigationDemo.kt
index bddb1e2..50d8776 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/BottomNavigationDemo.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/BottomNavigationDemo.kt
@@ -58,7 +58,7 @@
         ) {
             RadioButton(
                 selected = !alwaysShowLabels,
-                onClick = { alwaysShowLabels = false }
+                onClick = null
             )
             Spacer(Modifier.requiredWidth(16.dp))
             Text("Only show labels when selected")
@@ -75,7 +75,7 @@
         ) {
             RadioButton(
                 selected = alwaysShowLabels,
-                onClick = { alwaysShowLabels = true }
+                onClick = null
             )
             Spacer(Modifier.requiredWidth(16.dp))
             Text("Always show labels")
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
index d3c01a0..aeb2681 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
@@ -45,7 +45,13 @@
         ComposableDemo("App Bars") { AppBarDemo() },
         ComposableDemo("Backdrop") { BackdropScaffoldSample() },
         ComposableDemo("Bottom Navigation") { BottomNavigationDemo() },
-        ComposableDemo("Bottom Sheet") { BottomSheetScaffoldSample() },
+        DemoCategory(
+            "Bottom Sheets",
+            listOf(
+                ComposableDemo("Bottom Sheet") { BottomSheetScaffoldSample() },
+                ComposableDemo("Modal Bottom Sheet") { ModalBottomSheetSample() },
+            )
+        ),
         ComposableDemo("Buttons & FABs") { ButtonDemo() },
         DemoCategory(
             "Navigation drawer",
@@ -72,7 +78,6 @@
                 ActivityDemo("Dynamic Theme", DynamicThemeActivity::class)
             )
         ),
-        ComposableDemo("Modal bottom sheet") { ModalBottomSheetSample() },
         ComposableDemo("Progress Indicators") { ProgressIndicatorDemo() },
         DemoCategory(
             "Scaffold",
diff --git a/compose/material/material/integration-tests/material-studies/OWNERS b/compose/material/material/integration-tests/material-studies/OWNERS
deleted file mode 100644
index 890d52b..0000000
--- a/compose/material/material/integration-tests/material-studies/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
[email protected]
diff --git a/compose/material/material/integration-tests/material-studies/build.gradle b/compose/material/material/integration-tests/material-studies/build.gradle
deleted file mode 100644
index e3bfadb..0000000
--- a/compose/material/material/integration-tests/material-studies/build.gradle
+++ /dev/null
@@ -1,40 +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.
- */
-
-import static androidx.build.dependencies.DependenciesKt.*
-
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.library")
-    id("AndroidXUiPlugin")
-    id("org.jetbrains.kotlin.android")
-}
-
-dependencies {
-    kotlinPlugin(project(":compose:compiler:compiler"))
-
-    implementation(KOTLIN_STDLIB)
-
-    implementation(project(":compose:animation:animation"))
-    implementation(project(":compose:foundation:foundation"))
-    implementation(project(":compose:foundation:foundation-layout"))
-    implementation(project(":compose:integration-tests:demos:common"))
-    implementation(project(":compose:runtime:runtime"))
-    implementation(project(":compose:ui:ui"))
-    implementation(project(":compose:ui:ui-text"))
-    implementation(project(':compose:material:material'))
-    implementation(project(":activity:activity-compose"))
-}
diff --git a/compose/material/material/integration-tests/material-studies/lint-baseline.xml b/compose/material/material/integration-tests/material-studies/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/material/material/integration-tests/material-studies/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/material/material/integration-tests/material-studies/src/main/ic_launcher-web.png b/compose/material/material/integration-tests/material-studies/src/main/ic_launcher-web.png
deleted file mode 100644
index 88e5f3b..0000000
--- a/compose/material/material/integration-tests/material-studies/src/main/ic_launcher-web.png
+++ /dev/null
Binary files differ
diff --git a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/MaterialStudies.kt b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/MaterialStudies.kt
deleted file mode 100644
index a61fc7e..0000000
--- a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/MaterialStudies.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.material.studies
-
-import androidx.compose.material.studies.rally.RallyActivity
-import androidx.compose.integration.demos.common.ActivityDemo
-import androidx.compose.integration.demos.common.DemoCategory
-
-val MaterialStudies = DemoCategory(
-    "Material Studies",
-    listOf(
-        ActivityDemo("Rally", RallyActivity::class)
-    )
-)
diff --git a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/AccountsScreen.kt b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/AccountsScreen.kt
deleted file mode 100644
index da47d54..0000000
--- a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/AccountsScreen.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.material.studies.rally
-
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.Card
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-
-/**
- * The Accounts screen.
- */
-@Composable
-fun AccountsBody(accounts: List<Account>) {
-    Column {
-        Box(Modifier.verticalScroll(rememberScrollState(0)).padding(16.dp)) {
-            val accountsProportion = accounts.extractProportions { it.balance }
-            val colors = accounts.map { it.color }
-            AnimatedCircle(
-                Modifier.height(300.dp).align(Alignment.Center).fillMaxWidth(),
-                accountsProportion,
-                colors
-            )
-            Column(modifier = Modifier.align(Alignment.Center)) {
-                Text(
-                    text = "Total",
-                    style = MaterialTheme.typography.body1,
-                    modifier = Modifier.align(Alignment.CenterHorizontally)
-                )
-                Text(
-                    text = "$12,132.49",
-                    style = MaterialTheme.typography.h2,
-                    modifier = Modifier.align(Alignment.CenterHorizontally)
-                )
-            }
-        }
-        Spacer(Modifier.height(10.dp))
-        Card {
-            Column(modifier = Modifier.padding(12.dp)) {
-                accounts.forEach { account ->
-                    AccountRow(
-                        name = account.name,
-                        number = account.number,
-                        amount = account.balance,
-                        color = account.color
-                    )
-                }
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/BillsScreen.kt b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/BillsScreen.kt
deleted file mode 100644
index 90c7b13..0000000
--- a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/BillsScreen.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.material.studies.rally
-
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.Card
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-
-/**
- * The Bills screen.
- */
-@Composable
-fun BillsBody(bills: List<Bill>) {
-    Box(Modifier.verticalScroll(rememberScrollState()).padding(16.dp)) {
-        val accountsProportion = bills.extractProportions { it.amount }
-        val colors = bills.map { it.color }
-        AnimatedCircle(
-            Modifier.align(Alignment.Center).height(300.dp).fillMaxWidth(),
-            accountsProportion,
-            colors
-        )
-        Column(modifier = Modifier.align(Alignment.Center)) {
-            Text(
-                text = "Due",
-                style = MaterialTheme.typography.body1,
-                modifier = Modifier.align(Alignment.CenterHorizontally)
-            )
-            Text(
-                text = "$1,810.00",
-                style = MaterialTheme.typography.h2,
-                modifier = Modifier.align(Alignment.CenterHorizontally)
-            )
-        }
-    }
-    Spacer(Modifier.height(10.dp))
-    Card {
-        Column(modifier = Modifier.padding(12.dp)) {
-            bills.forEach { bill ->
-                BillRow(
-                    name = bill.name,
-                    due = bill.due,
-                    amount = bill.amount,
-                    color = bill.color
-                )
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/CommonUi.kt b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/CommonUi.kt
deleted file mode 100644
index d260582..0000000
--- a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/CommonUi.kt
+++ /dev/null
@@ -1,137 +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.compose.material.studies.rally
-
-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.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
-import androidx.compose.material.Divider
-import androidx.compose.material.Icon
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
-import androidx.compose.material.icons.Icons
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.unit.dp
-import java.text.DecimalFormat
-
-/**
- * A row representing the basic information of an Account.
- */
-@Composable
-fun AccountRow(name: String, number: Int, amount: Float, color: Color) {
-    BaseRow(
-        color = color,
-        title = name,
-        subtitle = "• • • • • " + accountDecimalFormat.format(number),
-        amount = amount,
-        negative = false
-    )
-}
-
-/**
- * A row representing the basic information of a Bill.
- */
-@Composable
-fun BillRow(name: String, due: String, amount: Float, color: Color) {
-    BaseRow(
-        color = color,
-        title = name,
-        subtitle = "Due $due",
-        amount = amount,
-        negative = true
-    )
-}
-
-@Composable
-private fun BaseRow(
-    color: Color,
-    title: String,
-    subtitle: String,
-    amount: Float,
-    negative: Boolean
-) {
-    Row(Modifier.height(68.dp)) {
-        val typography = MaterialTheme.typography
-        AccountIndicator(color = color, modifier = Modifier.align(Alignment.CenterVertically))
-        Spacer(Modifier.width(8.dp))
-        Column(Modifier.align(Alignment.CenterVertically)) {
-            Text(text = title, style = typography.body1)
-            Text(text = subtitle, style = typography.subtitle1)
-        }
-        Spacer(Modifier.weight(1f))
-        Row(
-            modifier = Modifier.align(Alignment.CenterVertically).width(113.dp),
-            horizontalArrangement = Arrangement.SpaceBetween
-        ) {
-            Text(
-                text = if (negative) "–$ " else "$ ",
-                style = typography.h6,
-                modifier = Modifier.align(Alignment.CenterVertically)
-            )
-            Text(
-                text = formatAmount(amount),
-                style = typography.h6,
-                modifier = Modifier.align(Alignment.CenterVertically)
-            )
-        }
-        Spacer(Modifier.width(16.dp))
-        Icon(
-            Icons.Filled.ArrowForwardIos,
-            contentDescription = null,
-            modifier = Modifier.size(12.dp).align(Alignment.CenterVertically),
-            tint = Color.White.copy(alpha = 0.6f)
-        )
-    }
-    RallyDivider()
-}
-
-/**
- * A vertical colored line that is used in a [BaseRow] to differentiate accounts.
- */
-@Composable
-private fun AccountIndicator(color: Color, modifier: Modifier = Modifier) {
-    Box(modifier.size(4.dp, 36.dp).background(color = color))
-}
-
-@Composable
-fun RallyDivider(modifier: Modifier = Modifier) {
-    Divider(color = MaterialTheme.colors.background, thickness = 1.dp, modifier = modifier)
-}
-
-fun formatAmount(amount: Float): String {
-    return amountDecimalFormat.format(amount)
-}
-
-private val accountDecimalFormat = DecimalFormat("####")
-private val amountDecimalFormat = DecimalFormat("#,###.##")
-
-/**
- * Used with accounts and bills to create the animated circle.
- */
-fun <E> List<E>.extractProportions(selector: (E) -> Float): List<Float> {
-    val total = this.sumByDouble { selector(it).toDouble() }
-    return this.map { (selector(it) / total).toFloat() }
-}
\ No newline at end of file
diff --git a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/Icons.kt b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/Icons.kt
deleted file mode 100644
index fc65d12..0000000
--- a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/Icons.kt
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.material.studies.rally
-
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.materialIcon
-import androidx.compose.material.icons.materialPath
-import androidx.compose.ui.graphics.vector.ImageVector
-
-/**
- * Icons below are copied from [Icons.Filled] in material-icons-extended to avoid recompiling the
- * module in demos. In the future when we release a stable artifact we could directly depend on
- * that, instead of a project dependency which causes recompilation.
- *
- * If the generated icons change, just build material-icons-extended and copy the generated
- * file, which should appear in Studio sources by searching for the name of that icon.
- */
-
-val Icons.Filled.Sort: ImageVector by lazy {
-    materialIcon("Filled.Sort") {
-        materialPath {
-            moveTo(3.0f, 18.0f)
-            horizontalLineToRelative(6.0f)
-            verticalLineToRelative(-2.0f)
-            lineTo(3.0f, 16.0f)
-            verticalLineToRelative(2.0f)
-            close()
-            moveTo(3.0f, 6.0f)
-            verticalLineToRelative(2.0f)
-            horizontalLineToRelative(18.0f)
-            lineTo(21.0f, 6.0f)
-            lineTo(3.0f, 6.0f)
-            close()
-            moveTo(3.0f, 13.0f)
-            horizontalLineToRelative(12.0f)
-            verticalLineToRelative(-2.0f)
-            lineTo(3.0f, 11.0f)
-            verticalLineToRelative(2.0f)
-            close()
-        }
-    }
-}
-
-val Icons.Filled.ArrowForwardIos: ImageVector by lazy {
-    materialIcon("Filled.ArrowForwardIos") {
-        materialPath {
-            moveTo(5.88f, 4.12f)
-            lineTo(13.76f, 12.0f)
-            lineToRelative(-7.88f, 7.88f)
-            lineTo(8.0f, 22.0f)
-            lineToRelative(10.0f, -10.0f)
-            lineTo(8.0f, 2.0f)
-            close()
-        }
-    }
-}
-
-val Icons.Filled.AttachMoney: ImageVector by lazy {
-    materialIcon("Filled.AttachMoney") {
-        materialPath {
-            moveTo(11.8f, 10.9f)
-            curveToRelative(-2.27f, -0.59f, -3.0f, -1.2f, -3.0f, -2.15f)
-            curveToRelative(0.0f, -1.09f, 1.01f, -1.85f, 2.7f, -1.85f)
-            curveToRelative(1.78f, 0.0f, 2.44f, 0.85f, 2.5f, 2.1f)
-            horizontalLineToRelative(2.21f)
-            curveToRelative(-0.07f, -1.72f, -1.12f, -3.3f, -3.21f, -3.81f)
-            verticalLineTo(3.0f)
-            horizontalLineToRelative(-3.0f)
-            verticalLineToRelative(2.16f)
-            curveToRelative(-1.94f, 0.42f, -3.5f, 1.68f, -3.5f, 3.61f)
-            curveToRelative(0.0f, 2.31f, 1.91f, 3.46f, 4.7f, 4.13f)
-            curveToRelative(2.5f, 0.6f, 3.0f, 1.48f, 3.0f, 2.41f)
-            curveToRelative(0.0f, 0.69f, -0.49f, 1.79f, -2.7f, 1.79f)
-            curveToRelative(-2.06f, 0.0f, -2.87f, -0.92f, -2.98f, -2.1f)
-            horizontalLineToRelative(-2.2f)
-            curveToRelative(0.12f, 2.19f, 1.76f, 3.42f, 3.68f, 3.83f)
-            verticalLineTo(21.0f)
-            horizontalLineToRelative(3.0f)
-            verticalLineToRelative(-2.15f)
-            curveToRelative(1.95f, -0.37f, 3.5f, -1.5f, 3.5f, -3.55f)
-            curveToRelative(0.0f, -2.84f, -2.43f, -3.81f, -4.7f, -4.4f)
-            close()
-        }
-    }
-}
-
-val Icons.Filled.MoneyOff: ImageVector by lazy {
-    materialIcon("Filled.MoneyOff") {
-        materialPath {
-            moveTo(12.5f, 6.9f)
-            curveToRelative(1.78f, 0.0f, 2.44f, 0.85f, 2.5f, 2.1f)
-            horizontalLineToRelative(2.21f)
-            curveToRelative(-0.07f, -1.72f, -1.12f, -3.3f, -3.21f, -3.81f)
-            verticalLineTo(3.0f)
-            horizontalLineToRelative(-3.0f)
-            verticalLineToRelative(2.16f)
-            curveToRelative(-0.53f, 0.12f, -1.03f, 0.3f, -1.48f, 0.54f)
-            lineToRelative(1.47f, 1.47f)
-            curveToRelative(0.41f, -0.17f, 0.91f, -0.27f, 1.51f, -0.27f)
-            close()
-            moveTo(5.33f, 4.06f)
-            lineTo(4.06f, 5.33f)
-            lineTo(7.5f, 8.77f)
-            curveToRelative(0.0f, 2.08f, 1.56f, 3.21f, 3.91f, 3.91f)
-            lineToRelative(3.51f, 3.51f)
-            curveToRelative(-0.34f, 0.48f, -1.05f, 0.91f, -2.42f, 0.91f)
-            curveToRelative(-2.06f, 0.0f, -2.87f, -0.92f, -2.98f, -2.1f)
-            horizontalLineToRelative(-2.2f)
-            curveToRelative(0.12f, 2.19f, 1.76f, 3.42f, 3.68f, 3.83f)
-            verticalLineTo(21.0f)
-            horizontalLineToRelative(3.0f)
-            verticalLineToRelative(-2.15f)
-            curveToRelative(0.96f, -0.18f, 1.82f, -0.55f, 2.45f, -1.12f)
-            lineToRelative(2.22f, 2.22f)
-            lineToRelative(1.27f, -1.27f)
-            lineTo(5.33f, 4.06f)
-            close()
-        }
-    }
-}
-
-val Icons.Filled.PieChart: ImageVector by lazy {
-    materialIcon("Filled.PieChart") {
-        materialPath {
-            moveTo(11.0f, 2.0f)
-            verticalLineToRelative(20.0f)
-            curveToRelative(-5.07f, -0.5f, -9.0f, -4.79f, -9.0f, -10.0f)
-            reflectiveCurveToRelative(3.93f, -9.5f, 9.0f, -10.0f)
-            close()
-            moveTo(13.03f, 2.0f)
-            verticalLineToRelative(8.99f)
-            lineTo(22.0f, 10.99f)
-            curveToRelative(-0.47f, -4.74f, -4.24f, -8.52f, -8.97f, -8.99f)
-            close()
-            moveTo(13.03f, 13.01f)
-            lineTo(13.03f, 22.0f)
-            curveToRelative(4.74f, -0.47f, 8.5f, -4.25f, 8.97f, -8.99f)
-            horizontalLineToRelative(-8.97f)
-            close()
-        }
-    }
-}
diff --git a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/OverviewScreen.kt b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/OverviewScreen.kt
deleted file mode 100644
index 04f9619..0000000
--- a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/OverviewScreen.kt
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.material.studies.rally
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.material.Card
-import androidx.compose.material.Divider
-import androidx.compose.material.Icon
-import androidx.compose.material.IconButton
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
-import androidx.compose.material.TextButton
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.studies.R
-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.res.stringResource
-import androidx.compose.ui.unit.dp
-import java.util.Locale
-
-@Composable
-fun OverviewBody() {
-    LazyColumn(
-        contentPadding = PaddingValues(16.dp),
-        verticalArrangement = Arrangement.spacedBy(RallyDefaultPadding)
-    ) {
-        item {
-            AlertCard()
-        }
-        item {
-            AccountsCard()
-        }
-        item {
-            BillsCard()
-        }
-    }
-}
-
-/**
- * The Alerts card within the Rally Overview screen.
- */
-@Composable
-private fun AlertCard() {
-    var openDialog by remember { mutableStateOf(false) }
-    val alertMessage = "Heads up, you've used up 90% of your Shopping budget for this month."
-
-    if (openDialog) {
-        RallyAlertDialog(
-            onDismiss = {
-                openDialog = false
-            },
-            bodyText = alertMessage,
-            buttonText = "Dismiss".toUpperCase(Locale.getDefault())
-        )
-    }
-    Card {
-        Column {
-            AlertHeader { openDialog = true }
-            RallyDivider(
-                modifier = Modifier.padding(start = RallyDefaultPadding, end = RallyDefaultPadding)
-            )
-            AlertItem(alertMessage)
-        }
-    }
-}
-
-@Composable
-private fun AlertHeader(onClickSeeAll: () -> Unit) {
-    Row(
-        modifier = Modifier.padding(RallyDefaultPadding).fillMaxWidth(),
-        horizontalArrangement = Arrangement.SpaceBetween
-    ) {
-        Text(
-            text = "Alerts",
-            style = MaterialTheme.typography.subtitle2,
-            modifier = Modifier.align(Alignment.CenterVertically)
-        )
-        TextButton(
-            onClick = onClickSeeAll,
-            contentPadding = PaddingValues(0.dp),
-            modifier = Modifier.align(Alignment.CenterVertically)
-        ) {
-            Text("SEE ALL")
-        }
-    }
-}
-
-@Composable
-private fun AlertItem(message: String) {
-    // TODO: Make alerts into a data structure
-    Row(
-        modifier = Modifier.padding(RallyDefaultPadding),
-        horizontalArrangement = Arrangement.SpaceBetween
-    ) {
-        Text(
-            style = MaterialTheme.typography.h3,
-            modifier = Modifier.weight(1f),
-            text = message
-        )
-        IconButton(
-            onClick = {},
-            modifier = Modifier.align(Alignment.Top)
-        ) {
-            Icon(Icons.Filled.Sort, contentDescription = stringResource(R.string.sort))
-        }
-    }
-}
-
-/**
- * Base structure for cards in the Overview screen.
- */
-@Composable
-private fun <T> OverviewScreenCard(
-    title: String,
-    amount: Float,
-    onClickSeeAll: () -> Unit,
-    data: List<T>,
-    content: @Composable (T) -> Unit
-) {
-    Card {
-        Column {
-            Column(Modifier.padding(RallyDefaultPadding)) {
-                Text(text = title, style = MaterialTheme.typography.subtitle2)
-                val amountText = "$" + formatAmount(amount)
-                Text(text = amountText, style = MaterialTheme.typography.h2)
-            }
-            Divider(color = rallyGreen, thickness = 1.dp)
-            Column(Modifier.padding(start = 16.dp, top = 4.dp, end = 8.dp)) {
-                data.take(3).forEach { content(it) }
-                SeeAllButton(onClick = onClickSeeAll)
-            }
-        }
-    }
-}
-
-/**
- * The Accounts card within the Rally Overview screen.
- */
-@Composable
-private fun AccountsCard() {
-    val amount = UserData.accounts.map { account -> account.balance }.sum()
-    OverviewScreenCard(
-        title = "Accounts",
-        amount = amount,
-        onClickSeeAll = {
-            // TODO: Figure out navigation
-        },
-        data = UserData.accounts
-    ) { account ->
-        AccountRow(
-            name = account.name,
-            number = account.number,
-            amount = account.balance,
-            color = account.color
-        )
-    }
-}
-
-/**
- * The Bills card within the Rally Overview screen.
- */
-@Composable
-private fun BillsCard() {
-    val amount = UserData.bills.map { bill -> bill.amount }.sum()
-    OverviewScreenCard(
-        title = "Bills",
-        amount = amount,
-        onClickSeeAll = {
-            // TODO: Figure out navigation
-        },
-        data = UserData.bills
-    ) { bill ->
-        BillRow(
-            name = bill.name,
-            due = bill.due,
-            amount = bill.amount,
-            color = bill.color
-        )
-    }
-}
-
-@Composable
-private fun SeeAllButton(onClick: () -> Unit) {
-    TextButton(
-        onClick = onClick,
-        modifier = Modifier.height(44.dp).fillMaxWidth()
-    ) {
-        Text("SEE ALL")
-    }
-}
-
-private val RallyDefaultPadding = 12.dp
diff --git a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyActivity.kt b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyActivity.kt
deleted file mode 100644
index 8471097..0000000
--- a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyActivity.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.material.studies.rally
-
-import android.os.Bundle
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material.Scaffold
-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
-
-/**
- * This Activity recreates the Rally Material Study from
- * https://material.io/design/material-studies/rally.html
- */
-class RallyActivity : ComponentActivity() {
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        setContent {
-            RallyApp()
-        }
-    }
-}
-
-@Composable
-fun RallyApp() {
-    RallyTheme {
-        val allScreens = RallyScreenState.values().toList()
-        var currentScreen by remember { mutableStateOf(RallyScreenState.Overview) }
-        Scaffold(
-            topBar = {
-                RallyTopAppBar(
-                    allScreens = allScreens,
-                    onTabSelected = { screen -> currentScreen = screen },
-                    currentScreen = currentScreen
-                )
-            }
-        ) { innerPadding ->
-            Box(Modifier.padding(innerPadding)) {
-                currentScreen.body()
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyAlertDialog.kt b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyAlertDialog.kt
deleted file mode 100644
index 3975dc7..0000000
--- a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyAlertDialog.kt
+++ /dev/null
@@ -1,61 +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.compose.material.studies.rally
-
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material.AlertDialog
-import androidx.compose.material.Divider
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
-import androidx.compose.material.TextButton
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.unit.dp
-
-@Composable
-fun RallyAlertDialog(
-    onDismiss: () -> Unit,
-    bodyText: String,
-    buttonText: String
-) {
-    RallyDialogThemeOverlay {
-        AlertDialog(
-            onDismissRequest = onDismiss,
-            text = { Text(bodyText) },
-            buttons = {
-                Column {
-                    Divider(
-                        Modifier.padding(12.dp, 0.dp, 12.dp, 0.dp),
-                        color = MaterialTheme.colors.onSurface.copy(alpha = 0.2f)
-                    )
-                    TextButton(
-                        onClick = onDismiss,
-                        shape = RectangleShape,
-                        contentPadding = PaddingValues(16.dp),
-                        modifier = Modifier.fillMaxWidth()
-                    ) {
-                        Text(buttonText)
-                    }
-                }
-            }
-        )
-    }
-}
diff --git a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyAnimatedCircle.kt b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyAnimatedCircle.kt
deleted file mode 100644
index 4fe42e6..0000000
--- a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyAnimatedCircle.kt
+++ /dev/null
@@ -1,103 +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.compose.material.studies.rally
-
-import androidx.compose.animation.core.CubicBezierEasing
-import androidx.compose.animation.core.LinearOutSlowInEasing
-import androidx.compose.animation.core.MutableTransitionState
-import androidx.compose.animation.core.animateFloat
-import androidx.compose.animation.core.spring
-import androidx.compose.animation.core.tween
-import androidx.compose.animation.core.updateTransition
-import androidx.compose.foundation.Canvas
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.drawscope.Stroke
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.unit.dp
-
-private const val DividerLengthInDegrees = 1.8f
-
-/** when calculating a proportion of N elements, the sum of elements has to be (1 - N * 0.005)
- * because there will be N dividers of size 1.8 degrees */
-@Composable
-fun AnimatedCircle(
-    modifier: Modifier = Modifier,
-    proportions: List<Float>,
-    colors: List<Color>
-) {
-    val stroke = Stroke(5.dp.value * LocalDensity.current.density)
-    // Start animating when added to the tree
-    val states = remember { MutableTransitionState(0).apply { targetState = 1 } }
-    val transition = updateTransition(states)
-    val angleOffset by transition.animateFloat(
-        transitionSpec = {
-            if (0 isTransitioningTo 1) {
-                tween(
-                    delayMillis = 500,
-                    durationMillis = 900,
-                    easing = CubicBezierEasing(0f, 0.75f, 0.35f, 0.85f)
-                )
-            } else {
-                spring()
-            }
-        }
-    ) { if (it == 1) 360f else 0f }
-    val shift by transition.animateFloat(
-        transitionSpec = {
-            if (0 isTransitioningTo 1) {
-                tween(
-                    delayMillis = 500,
-                    durationMillis = 900,
-                    easing = LinearOutSlowInEasing
-                )
-            } else {
-                spring()
-            }
-        }
-    ) {
-        if (it == 1) 30f else 0f
-    }
-    Canvas(modifier) {
-        val innerRadius = (size.minDimension - stroke.width) / 2
-        val halfSize = size / 2.0f
-        val topLeft = Offset(
-            halfSize.width - innerRadius,
-            halfSize.height - innerRadius
-        )
-        val size = Size(innerRadius * 2, innerRadius * 2)
-        var startAngle = shift - 90f
-        proportions.forEachIndexed { index, proportion ->
-            val sweep = proportion * angleOffset
-            drawArc(
-                color = colors[index],
-                startAngle = startAngle + DividerLengthInDegrees / 2,
-                sweepAngle = sweep - DividerLengthInDegrees,
-                topLeft = topLeft,
-                size = size,
-                useCenter = false,
-                style = stroke
-            )
-            startAngle += sweep
-        }
-    }
-}
\ No newline at end of file
diff --git a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyData.kt b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyData.kt
deleted file mode 100644
index 2bb6319..0000000
--- a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyData.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.material.studies.rally
-
-import androidx.compose.ui.graphics.Color
-
-data class Account(
-    val name: String,
-    val number: Int,
-    val balance: Float,
-    val color: Color
-)
-
-data class Bill(
-    val name: String,
-    val due: String,
-    val amount: Float,
-    val color: Color
-)
-
-object UserData {
-    val accounts: List<Account> = listOf(
-        Account("Checking", 1234, 2215.13f, Color(0xFF005D57)),
-        Account("Home Savings", 5678, 8676.88f, Color(0xFF005D57)),
-        Account("Car Savings", 9012, 987.48f, Color(0xFF04B97F)),
-        Account("Vacation", 3456, 253f, Color(0xFF37EFBA))
-    )
-    val bills: List<Bill> = listOf(
-        Bill("RedPay Credit", "Jan 29", 45.36f, Color(0xFFFFDC78)),
-        Bill("Rent", "Feb 9", 1200f, Color(0xFFFF6951)),
-        Bill("TabFine Credit", "Feb 22", 87.33f, Color(0xFFFFD7D0)),
-        Bill("ABC Loans", "Feb 29", 400f, Color(0xFFFFAC12)),
-        Bill("ABC Loans 2", "Feb 29", 77.4f, Color(0xFFFFAC12))
-    )
-}
\ No newline at end of file
diff --git a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyScreenState.kt b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyScreenState.kt
deleted file mode 100644
index ae6e831..0000000
--- a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyScreenState.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.material.studies.rally
-
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.studies.R
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.graphics.vector.ImageVector
-
-enum class RallyScreenState(
-    val icon: ScreenIcon,
-    val body: @Composable () -> Unit
-) {
-    Overview(
-        ScreenIcon(Icons.Filled.PieChart, contentDescription = R.string.overview),
-        @Composable { OverviewBody() }
-    ),
-    Accounts(
-        ScreenIcon(Icons.Filled.AttachMoney, contentDescription = R.string.account),
-        @Composable { AccountsBody(UserData.accounts) }
-    ),
-    Bills(
-        ScreenIcon(Icons.Filled.MoneyOff, contentDescription = R.string.bills),
-        @Composable { BillsBody(UserData.bills) }
-    )
-}
-
-class ScreenIcon(val icon: ImageVector, val contentDescription: Int)
\ No newline at end of file
diff --git a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyTheme.kt b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyTheme.kt
deleted file mode 100644
index 14717ff..0000000
--- a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/RallyTheme.kt
+++ /dev/null
@@ -1,125 +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.compose.material.studies.rally
-
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Typography
-import androidx.compose.material.darkColors
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.em
-import androidx.compose.ui.unit.sp
-
-val rallyGreen = Color(0xFF1EB980)
-
-@Composable
-fun RallyTheme(content: @Composable () -> Unit) {
-    val colors = darkColors(
-        primary = Color.White,
-        surface = Color(0xFF26282F),
-        onSurface = Color.White,
-        background = Color(0xFF26282F),
-        onBackground = Color.White
-    )
-    // TODO: Bundle Roboto Condensed and Eczar font files.
-    val typography = Typography(
-        defaultFontFamily = FontFamily.Default,
-        // Unused
-        h1 = TextStyle(
-            fontWeight = FontWeight.W100,
-            fontSize = 96.sp
-        ),
-        h2 = TextStyle(
-            fontWeight = FontWeight.W600,
-            fontSize = 44.sp
-        ),
-        h3 = TextStyle(
-            fontWeight = FontWeight.W400,
-            fontSize = 14.sp
-        ),
-        // Unused
-        h4 = TextStyle(
-            fontWeight = FontWeight.W700,
-            fontSize = 34.sp
-        ),
-        // Unused
-        h5 = TextStyle(
-            fontWeight = FontWeight.W700,
-            fontSize = 24.sp
-        ),
-        // Eczar
-        h6 = TextStyle(
-            fontWeight = FontWeight.Normal,
-            fontSize = 18.sp
-        ),
-        subtitle1 = TextStyle(
-            fontWeight = FontWeight.W300,
-            fontSize = 14.sp
-        ),
-        subtitle2 = TextStyle(
-            fontWeight = FontWeight.W400,
-            fontSize = 14.sp
-        ),
-        body1 = TextStyle(
-            fontWeight = FontWeight.Normal,
-            fontSize = 16.sp
-        ),
-        body2 = TextStyle(
-            fontWeight = FontWeight.W200,
-            fontSize = 14.sp
-        ),
-        button = TextStyle(
-            fontWeight = FontWeight.W700,
-            fontSize = 14.sp
-        ),
-        // Unused
-        caption = TextStyle(
-            fontWeight = FontWeight.W500,
-            fontSize = 12.sp
-        ),
-        // Unused
-        overline = TextStyle(
-            fontWeight = FontWeight.W500,
-            fontSize = 10.sp
-        )
-    )
-    MaterialTheme(colors = colors, typography = typography, content = content)
-}
-
-@Composable
-fun RallyDialogThemeOverlay(content: @Composable () -> Unit) {
-    val dialogColors = darkColors(
-        primary = Color.White,
-        surface = Color(0xFF1E1E1E),
-        onSurface = Color.White
-    )
-    val currentTypography = MaterialTheme.typography
-    val dialogTypography = currentTypography.copy(
-        body1 = currentTypography.body1.copy(
-            fontWeight = FontWeight.Normal,
-            fontSize = 20.sp
-        ),
-        button = currentTypography.button.copy(
-            fontWeight = FontWeight.Bold,
-            letterSpacing = 0.2.em
-        )
-    )
-    MaterialTheme(colors = dialogColors, typography = dialogTypography, content = content)
-}
diff --git a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/TopAppBar.kt b/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/TopAppBar.kt
deleted file mode 100644
index 27debe0..0000000
--- a/compose/material/material/integration-tests/material-studies/src/main/java/androidx/compose/material/studies/rally/TopAppBar.kt
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.material.studies.rally
-
-import androidx.compose.animation.animateColor
-import androidx.compose.animation.core.LinearEasing
-import androidx.compose.animation.core.tween
-import androidx.compose.animation.core.updateTransition
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.selection.selectable
-import androidx.compose.material.Icon
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Surface
-import androidx.compose.material.Text
-import androidx.compose.material.ripple.rememberRipple
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import java.util.Locale
-
-@Composable
-fun RallyTopAppBar(
-    allScreens: List<RallyScreenState>,
-    onTabSelected: (RallyScreenState) -> Unit,
-    currentScreen: RallyScreenState
-) {
-    Surface(Modifier.height(TabHeight).fillMaxWidth()) {
-        Row {
-            allScreens.forEachIndexed { index, screen ->
-                RallyTab(
-                    text = screen.name.toUpperCase(Locale.getDefault()),
-                    icon = screen.icon,
-                    onSelected = { onTabSelected(screen) },
-                    selected = currentScreen.ordinal == index
-                )
-            }
-        }
-    }
-}
-
-@Composable
-private fun RallyTab(
-    text: String,
-    icon: ScreenIcon,
-    onSelected: () -> Unit,
-    selected: Boolean
-) {
-    TabTransition(selected = selected) { tabTintColor ->
-        Row(
-            modifier = Modifier
-                .padding(16.dp)
-                .height(TabHeight)
-                .selectable(
-                    selected = selected,
-                    onClick = onSelected,
-                    interactionSource = remember { MutableInteractionSource() },
-                    indication = rememberRipple(bounded = false)
-                )
-        ) {
-            Icon(
-                imageVector = icon.icon,
-                contentDescription = stringResource(icon.contentDescription),
-                tint = tabTintColor
-            )
-            if (selected) {
-                Spacer(Modifier.width(12.dp))
-                Text(text, color = tabTintColor)
-            }
-        }
-    }
-}
-
-@Composable
-private fun TabTransition(
-    selected: Boolean,
-    content: @Composable (color: Color) -> Unit
-) {
-    val transition = updateTransition(selected)
-    val tintColor by transition.animateColor(
-        transitionSpec = {
-            if (true isTransitioningTo false) {
-                tween(
-                    durationMillis = TabFadeOutAnimationDuration,
-                    delayMillis = TabFadeInAnimationDelay,
-                    easing = LinearEasing
-                )
-            } else {
-                tween(
-                    durationMillis = TabFadeInAnimationDuration,
-                    delayMillis = TabFadeInAnimationDelay,
-                    easing = LinearEasing
-                )
-            }
-        }
-    ) {
-        if (it) {
-            MaterialTheme.colors.onSurface
-        } else {
-            MaterialTheme.colors.onSurface.copy(alpha = InactiveTabOpacity)
-        }
-    }
-    content(tintColor)
-}
-
-private val TabHeight = 56.dp
-private const val InactiveTabOpacity = 0.60f
-
-private const val TabFadeInAnimationDuration = 150
-private const val TabFadeInAnimationDelay = 100
-private const val TabFadeOutAnimationDuration = 100
\ No newline at end of file
diff --git a/compose/material/material/integration-tests/material-studies/src/main/res/drawable/material_logo.xml b/compose/material/material/integration-tests/material-studies/src/main/res/drawable/material_logo.xml
deleted file mode 100644
index 8d29e8b..0000000
--- a/compose/material/material/integration-tests/material-studies/src/main/res/drawable/material_logo.xml
+++ /dev/null
@@ -1,22 +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.
-  -->
-
-<vector android:height="24dp" android:viewportHeight="192"
-    android:viewportWidth="192" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="#757575" android:pathData="M96,96m-96,0a96,96 0,1 1,192 0a96,96 0,1 1,-192 0"/>
-    <path android:fillColor="#bdbdbd" android:pathData="M29,29h134v134H29z"/>
-    <path android:fillColor="#fff" android:pathData="M163,29L96,163 29,29h134z"/>
-</vector>
diff --git a/compose/material/material/lint-baseline.xml b/compose/material/material/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/material/material/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/material/material/samples/lint-baseline.xml b/compose/material/material/samples/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/material/material/samples/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/DrawerSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/DrawerSamples.kt
index 7175e72..5e3e453 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/DrawerSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/DrawerSamples.kt
@@ -18,20 +18,31 @@
 
 import androidx.annotation.Sampled
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.selection.toggleable
 import androidx.compose.material.BottomDrawer
 import androidx.compose.material.BottomDrawerValue
 import androidx.compose.material.Button
+import androidx.compose.material.Checkbox
 import androidx.compose.material.DrawerValue
 import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.Icon
+import androidx.compose.material.ListItem
 import androidx.compose.material.ModalDrawer
 import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.material.rememberBottomDrawerState
 import androidx.compose.material.rememberDrawerState
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -71,28 +82,55 @@
 @Composable
 @OptIn(ExperimentalMaterialApi::class)
 fun BottomDrawerSample() {
-    val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
+    val (gesturesEnabled, toggleGesturesEnabled) = remember { mutableStateOf(true) }
     val scope = rememberCoroutineScope()
-    BottomDrawer(
-        drawerState = drawerState,
-        drawerContent = {
-            Button(
-                modifier = Modifier.align(Alignment.CenterHorizontally).padding(top = 16.dp),
-                onClick = { scope.launch { drawerState.close() } },
-                content = { Text("Close Drawer") }
+    Column {
+        Row(
+            modifier = Modifier.fillMaxWidth().toggleable(
+                value = gesturesEnabled,
+                onValueChange = toggleGesturesEnabled
             )
-        },
-        content = {
-            Column(
-                modifier = Modifier.fillMaxSize().padding(16.dp),
-                horizontalAlignment = Alignment.CenterHorizontally
-            ) {
-                Text(text = if (drawerState.isClosed) "▲▲▲ Pull up ▲▲▲" else "▼▼▼ Drag down ▼▼▼")
-                Spacer(Modifier.height(20.dp))
-                Button(onClick = { scope.launch { drawerState.open() } }) {
-                    Text("Click to open")
+        ) {
+            Checkbox(gesturesEnabled, null)
+            Text(text = if (gesturesEnabled) "Gestures Enabled" else "Gestures Disabled")
+        }
+        val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
+        BottomDrawer(
+            gesturesEnabled = gesturesEnabled,
+            drawerState = drawerState,
+            drawerContent = {
+                Button(
+                    modifier = Modifier.align(Alignment.CenterHorizontally).padding(top = 16.dp),
+                    onClick = { scope.launch { drawerState.close() } },
+                    content = { Text("Close Drawer") }
+                )
+                LazyColumn {
+                    items(5) {
+                        ListItem(
+                            text = { Text("Item $it") },
+                            icon = {
+                                Icon(
+                                    Icons.Default.Favorite,
+                                    contentDescription = "Localized description"
+                                )
+                            }
+                        )
+                    }
+                }
+            },
+            content = {
+                Column(
+                    modifier = Modifier.fillMaxSize().padding(16.dp),
+                    horizontalAlignment = Alignment.CenterHorizontally
+                ) {
+                    val openText = if (gesturesEnabled) "▲▲▲ Pull up ▲▲▲" else "Click the button!"
+                    Text(text = if (drawerState.isClosed) openText else "▼▼▼ Drag down ▼▼▼")
+                    Spacer(Modifier.height(20.dp))
+                    Button(onClick = { scope.launch { drawerState.open() } }) {
+                        Text("Click to open")
+                    }
                 }
             }
-        }
-    )
+        )
+    }
 }
\ No newline at end of file
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 74681ac..6946763 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
@@ -21,12 +21,15 @@
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.SemanticsMatcher
 import androidx.compose.ui.test.assert
 import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
@@ -50,8 +53,10 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.MediumTest
+import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.runBlocking
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -64,6 +69,13 @@
     @get:Rule
     val rule = createComposeRule()
 
+    private val bottomDrawerTag = "drawerContentTag"
+    private val shortBottomDrawerHeight = 256.dp
+
+    private fun advanceClock() {
+        rule.mainClock.advanceTimeBy(100_000L)
+    }
+
     @Test
     fun modalDrawer_testOffset_whenOpen() {
         rule.setMaterialContent {
@@ -117,13 +129,70 @@
     }
 
     @Test
-    fun bottomDrawer_testOffset_whenOpen() {
+    fun bottomDrawer_testOffset_shortDrawer_whenClosed() {
+        rule.setMaterialContent {
+            val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
+            BottomDrawer(
+                drawerState = drawerState,
+                drawerContent = {
+                    Box(Modifier.fillMaxSize().testTag(bottomDrawerTag))
+                },
+                content = {}
+            )
+        }
+
+        val height = rule.rootHeight()
+        rule.onNodeWithTag(bottomDrawerTag)
+            .assertTopPositionInRootIsEqualTo(height)
+    }
+
+    @Test
+    fun bottomDrawer_testOffset_shortDrawer_whenExpanded() {
+        rule.setMaterialContent {
+            val drawerState = rememberBottomDrawerState(BottomDrawerValue.Expanded)
+            BottomDrawer(
+                drawerState = drawerState,
+                drawerContent = {
+                    Box(Modifier.height(shortBottomDrawerHeight).testTag(bottomDrawerTag))
+                },
+                content = {}
+            )
+        }
+
+        val height = rule.rootHeight()
+        val expectedTop = height - shortBottomDrawerHeight
+        rule.onNodeWithTag(bottomDrawerTag)
+            .assertTopPositionInRootIsEqualTo(expectedTop)
+    }
+
+    @Test
+    fun bottomDrawer_testOffset_tallDrawer_whenClosed() {
+        rule.setMaterialContent {
+            val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
+            BottomDrawer(
+                drawerState = drawerState,
+                drawerContent = {
+                    Box(Modifier.fillMaxSize().testTag(bottomDrawerTag))
+                },
+                content = {}
+            )
+        }
+
+        val height = rule.rootHeight()
+        val expectedTop = height
+        rule.onNodeWithTag(bottomDrawerTag)
+            .assertTopPositionInRootIsEqualTo(expectedTop)
+    }
+
+    @Test
+    @Ignore // Disabled until b/178529942 is fixed
+    fun bottomDrawer_testOffset_tallDrawer_whenOpen() {
         rule.setMaterialContent {
             val drawerState = rememberBottomDrawerState(BottomDrawerValue.Open)
             BottomDrawer(
                 drawerState = drawerState,
                 drawerContent = {
-                    Box(Modifier.fillMaxSize().testTag("content"))
+                    Box(Modifier.fillMaxSize().testTag(bottomDrawerTag))
                 },
                 content = {}
             )
@@ -132,26 +201,44 @@
         val width = rule.rootWidth()
         val height = rule.rootHeight()
         val expectedTop = if (width > height) 0.dp else (height / 2)
-        rule.onNodeWithTag("content")
+        rule.onNodeWithTag(bottomDrawerTag)
             .assertTopPositionInRootIsEqualTo(expectedTop)
     }
 
     @Test
-    fun bottomDrawer_testOffset_whenClosed() {
+    fun bottomDrawer_testOffset_tallDrawer_whenExpanded() {
         rule.setMaterialContent {
-            val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
+            val drawerState = rememberBottomDrawerState(BottomDrawerValue.Expanded)
             BottomDrawer(
                 drawerState = drawerState,
                 drawerContent = {
-                    Box(Modifier.fillMaxSize().testTag("content"))
+                    Box(Modifier.fillMaxSize().testTag(bottomDrawerTag))
                 },
                 content = {}
             )
         }
 
-        val height = rule.rootHeight()
-        rule.onNodeWithTag("content")
-            .assertTopPositionInRootIsEqualTo(height)
+        val expectedTop = 0.dp
+        rule.onNodeWithTag(bottomDrawerTag)
+            .assertTopPositionInRootIsEqualTo(expectedTop)
+    }
+
+    @Test
+    @SmallTest
+    fun bottomDrawer_hasPaneTitle() {
+        rule.setMaterialContent {
+            BottomDrawer(
+                drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed),
+                drawerContent = {
+                    Box(Modifier.fillMaxSize().testTag(bottomDrawerTag))
+                },
+                content = {}
+            )
+        }
+
+        rule.onNodeWithTag(bottomDrawerTag, useUnmergedTree = true)
+            .onParent()
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsProperties.PaneTitle))
     }
 
     @Test
@@ -263,125 +350,6 @@
 
     @Test
     @LargeTest
-    fun bottomDrawer_drawerContent_doesntPropagateClicksWhenOpen(): Unit = runBlocking {
-        var bodyClicks = 0
-        lateinit var drawerState: BottomDrawerState
-        rule.setMaterialContent {
-            drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
-            BottomDrawer(
-                drawerState = drawerState,
-                drawerContent = {
-                    Box(Modifier.fillMaxSize().testTag("Drawer"))
-                },
-                content = {
-                    Box(Modifier.fillMaxSize().clickable { bodyClicks += 1 })
-                }
-            )
-        }
-
-        // Click in the middle of the drawer
-        rule.onNodeWithTag("Drawer").performClick()
-
-        rule.runOnIdle {
-            assertThat(bodyClicks).isEqualTo(1)
-        }
-        drawerState.open()
-
-        // Click on the left-center pixel of the drawer
-        rule.onNodeWithTag("Drawer").performGesture {
-            click(centerLeft)
-        }
-
-        rule.runOnIdle {
-            assertThat(bodyClicks).isEqualTo(1)
-        }
-        drawerState.expand()
-
-        // Click on the left-center pixel of the drawer once again in a new state
-        rule.onNodeWithTag("Drawer").performGesture {
-            click(centerLeft)
-        }
-
-        rule.runOnIdle {
-            assertThat(bodyClicks).isEqualTo(1)
-        }
-    }
-
-    @Test
-    @LargeTest
-    fun bottomDrawer_openAndClose(): Unit = runBlocking {
-        lateinit var drawerState: BottomDrawerState
-        rule.setMaterialContent {
-            drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
-            BottomDrawer(
-                drawerState = drawerState,
-                drawerContent = {
-                    Box(Modifier.fillMaxSize().testTag("drawer"))
-                },
-                content = {}
-            )
-        }
-
-        val width = rule.rootWidth()
-        val height = rule.rootHeight()
-        val topWhenOpened = if (width > height) 0.dp else (height / 2)
-        val topWhenClosed = height
-
-        // Drawer should start in closed state
-        rule.onNodeWithTag("drawer").assertTopPositionInRootIsEqualTo(topWhenClosed)
-
-        // When the drawer state is set to Opened
-        drawerState.open()
-        // Then the drawer should be opened
-        rule.onNodeWithTag("drawer").assertTopPositionInRootIsEqualTo(topWhenOpened)
-
-        // When the drawer state is set to Closed
-        drawerState.close()
-        // Then the drawer should be closed
-        rule.onNodeWithTag("drawer").assertTopPositionInRootIsEqualTo(topWhenClosed)
-    }
-
-    @Test
-    fun bottomDrawer_bodyContent_clickable(): Unit = runBlocking {
-        var drawerClicks = 0
-        var bodyClicks = 0
-        lateinit var drawerState: BottomDrawerState
-        rule.setMaterialContent {
-            drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
-            // emulate click on the screen
-            BottomDrawer(
-                drawerState = drawerState,
-                drawerContent = {
-                    Box(Modifier.fillMaxSize().clickable { drawerClicks += 1 })
-                },
-                content = {
-                    Box(Modifier.testTag("Drawer").fillMaxSize().clickable { bodyClicks += 1 })
-                }
-            )
-        }
-
-        // Click in the middle of the drawer (which is the middle of the body)
-        rule.onNodeWithTag("Drawer").performGesture { click() }
-
-        rule.runOnIdle {
-            assertThat(drawerClicks).isEqualTo(0)
-            assertThat(bodyClicks).isEqualTo(1)
-        }
-
-        drawerState.open()
-        sleep(100) // TODO(147586311): remove this sleep when opening the drawer triggers a wait
-
-        // Click on the bottom-center pixel of the drawer
-        rule.onNodeWithTag("Drawer").performGesture {
-            click(bottomCenter)
-        }
-
-        assertThat(drawerClicks).isEqualTo(1)
-        assertThat(bodyClicks).isEqualTo(1)
-    }
-
-    @Test
-    @LargeTest
     fun modalDrawer_openBySwipe() {
         lateinit var drawerState: DrawerState
         rule.setMaterialContent {
@@ -453,43 +421,6 @@
 
     @Test
     @LargeTest
-    fun bottomDrawer_openBySwipe() {
-        lateinit var drawerState: BottomDrawerState
-        rule.setMaterialContent {
-            drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
-            // emulate click on the screen
-            Box(Modifier.testTag("Drawer")) {
-                BottomDrawer(
-                    drawerState = drawerState,
-                    drawerContent = {
-                        Box(Modifier.fillMaxSize().background(color = Color.Magenta))
-                    },
-                    content = {
-                        Box(Modifier.fillMaxSize().background(color = Color.Red))
-                    }
-                )
-            }
-        }
-        val isLandscape = rule.rootWidth() > rule.rootHeight()
-
-        rule.onNodeWithTag("Drawer")
-            .performGesture { swipeUp() }
-
-        rule.runOnIdle {
-            assertThat(drawerState.currentValue).isEqualTo(
-                if (isLandscape) BottomDrawerValue.Open else BottomDrawerValue.Expanded
-            )
-        }
-
-        rule.onNodeWithTag("Drawer")
-            .performGesture { swipeDown() }
-        rule.runOnIdle {
-            assertThat(drawerState.currentValue).isEqualTo(BottomDrawerValue.Closed)
-        }
-    }
-
-    @Test
-    @LargeTest
     fun modalDrawer_noDismissActionWhenClosed_hasDissmissActionWhenOpen(): Unit = runBlocking {
         lateinit var drawerState: DrawerState
         rule.setMaterialContent {
@@ -526,6 +457,257 @@
     }
 
     @Test
+    fun bottomDrawer_bodyContent_clickable(): Unit = runBlocking {
+        var drawerClicks = 0
+        var bodyClicks = 0
+        lateinit var drawerState: BottomDrawerState
+        rule.setMaterialContent {
+            drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
+            // emulate click on the screen
+            BottomDrawer(
+                drawerState = drawerState,
+                drawerContent = {
+                    Box(Modifier.fillMaxSize().clickable { drawerClicks += 1 })
+                },
+                content = {
+                    Box(
+                        Modifier
+                            .testTag(bottomDrawerTag)
+                            .fillMaxSize()
+                            .clickable { bodyClicks += 1 }
+                    )
+                }
+            )
+        }
+
+        // Click in the middle of the drawer (which is the middle of the body)
+        rule.onNodeWithTag(bottomDrawerTag).performGesture { click() }
+
+        rule.runOnIdle {
+            assertThat(drawerClicks).isEqualTo(0)
+            assertThat(bodyClicks).isEqualTo(1)
+        }
+
+        drawerState.open()
+        sleep(100) // TODO(147586311): remove this sleep when opening the drawer triggers a wait
+
+        // Click on the bottom-center pixel of the drawer
+        rule.onNodeWithTag(bottomDrawerTag).performGesture {
+            click(bottomCenter)
+        }
+
+        assertThat(drawerClicks).isEqualTo(1)
+        assertThat(bodyClicks).isEqualTo(1)
+    }
+
+    @Test
+    @LargeTest
+    fun bottomDrawer_drawerContent_doesntPropagateClicksWhenOpen(): Unit = runBlocking {
+        var bodyClicks = 0
+        lateinit var drawerState: BottomDrawerState
+        rule.setMaterialContent {
+            drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
+            BottomDrawer(
+                drawerState = drawerState,
+                drawerContent = {
+                    Box(Modifier.fillMaxSize().testTag(bottomDrawerTag))
+                },
+                content = {
+                    Box(Modifier.fillMaxSize().clickable { bodyClicks += 1 })
+                }
+            )
+        }
+
+        // Click in the middle of the drawer
+        rule.onNodeWithTag(bottomDrawerTag).performClick()
+
+        rule.runOnIdle {
+            assertThat(bodyClicks).isEqualTo(1)
+        }
+        drawerState.open()
+
+        // Click on the left-center pixel of the drawer
+        rule.onNodeWithTag(bottomDrawerTag).performGesture {
+            click(centerLeft)
+        }
+
+        rule.runOnIdle {
+            assertThat(bodyClicks).isEqualTo(1)
+        }
+        drawerState.expand()
+
+        // Click on the left-center pixel of the drawer once again in a new state
+        rule.onNodeWithTag(bottomDrawerTag).performGesture {
+            click(centerLeft)
+        }
+
+        rule.runOnIdle {
+            assertThat(bodyClicks).isEqualTo(1)
+        }
+    }
+
+    @Test
+    @LargeTest
+    fun bottomDrawer_openBySwipe_shortDrawer(): Unit = runBlocking {
+        val contentTag = "contentTestTag"
+        lateinit var drawerState: BottomDrawerState
+        rule.setMaterialContent {
+            drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
+            BottomDrawer(
+                drawerState = drawerState,
+                drawerContent = {
+                    Box(
+                        Modifier.height(shortBottomDrawerHeight).testTag(bottomDrawerTag)
+                    )
+                },
+                content = { Box(Modifier.fillMaxSize().testTag(contentTag)) }
+            )
+        }
+
+        rule.runOnIdle {
+            assertThat(drawerState.currentValue).isEqualTo(BottomDrawerValue.Closed)
+        }
+
+        rule.onNodeWithTag(contentTag)
+            .performGesture { swipeUp() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            assertThat(drawerState.currentValue).isEqualTo(BottomDrawerValue.Expanded)
+        }
+
+        rule.onNodeWithTag(bottomDrawerTag)
+            .performGesture { swipeDown() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            assertThat(drawerState.currentValue).isEqualTo(BottomDrawerValue.Closed)
+        }
+    }
+
+    @Test
+    @LargeTest
+    fun bottomDrawer_expandBySwipe_tallDrawer(): Unit = runBlocking {
+        val contentTag = "contentTestTag"
+        lateinit var drawerState: BottomDrawerState
+        rule.setMaterialContent {
+            drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
+            BottomDrawer(
+                drawerState = drawerState,
+                drawerContent = {
+                    Box(
+                        Modifier.fillMaxSize().testTag(bottomDrawerTag)
+                    )
+                },
+                content = { Box(Modifier.fillMaxSize().testTag(contentTag)) }
+            )
+        }
+
+        val isLandscape = rule.rootWidth() > rule.rootHeight()
+        val peekHeight = with(rule.density) { rule.rootHeight().toPx() / 2 }
+
+        rule.runOnIdle {
+            assertThat(drawerState.currentValue).isEqualTo(BottomDrawerValue.Closed)
+        }
+
+        @OptIn(ExperimentalTestApi::class)
+        rule.onNodeWithTag(contentTag)
+            .performGesture { swipeUp(endY = peekHeight) }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            assertThat(drawerState.currentValue).isEqualTo(
+                if (isLandscape) BottomDrawerValue.Expanded else BottomDrawerValue.Open
+            )
+        }
+
+        rule.onNodeWithTag(bottomDrawerTag)
+            .performGesture { swipeUp() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            assertThat(drawerState.currentValue).isEqualTo(BottomDrawerValue.Expanded)
+        }
+
+        @OptIn(ExperimentalTestApi::class)
+        rule.onNodeWithTag(bottomDrawerTag)
+            .performGesture { swipeDown(endY = peekHeight) }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            assertThat(drawerState.currentValue).isEqualTo(
+                if (isLandscape) BottomDrawerValue.Closed else BottomDrawerValue.Open
+            )
+        }
+
+        rule.onNodeWithTag(bottomDrawerTag)
+            .performGesture { swipeDown() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            assertThat(drawerState.currentValue).isEqualTo(BottomDrawerValue.Closed)
+        }
+    }
+
+    @Test
+    fun bottomDrawer_openBySwipe_onBodyContent(): Unit = runBlocking {
+        val contentTag = "contentTestTag"
+        lateinit var drawerState: BottomDrawerState
+        rule.setMaterialContent {
+            drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
+            BottomDrawer(
+                drawerState = drawerState,
+                drawerContent = { Box(Modifier.height(shortBottomDrawerHeight)) },
+                content = { Box(Modifier.fillMaxSize().testTag(contentTag)) }
+            )
+        }
+
+        rule.runOnIdle {
+            assertThat(drawerState.currentValue).isEqualTo(BottomDrawerValue.Closed)
+        }
+
+        rule.onNodeWithTag(contentTag)
+            .performGesture { swipeUp() }
+
+        advanceClock()
+
+        rule.runOnIdle {
+            assertThat(drawerState.currentValue).isEqualTo(BottomDrawerValue.Expanded)
+        }
+    }
+
+    @Test
+    fun bottomDrawer_hasDismissAction_whenExpanded(): Unit = runBlocking {
+        lateinit var drawerState: BottomDrawerState
+        rule.setMaterialContent {
+            drawerState = rememberBottomDrawerState(BottomDrawerValue.Expanded)
+            BottomDrawer(
+                drawerState = drawerState,
+                drawerContent = {
+                    Box(Modifier.fillMaxSize().testTag(bottomDrawerTag))
+                },
+                content = {}
+            )
+        }
+
+        val height = rule.rootHeight()
+        rule.onNodeWithTag(bottomDrawerTag).onParent()
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.Dismiss))
+            .performSemanticsAction(SemanticsActions.Dismiss)
+
+        advanceClock()
+
+        rule.onNodeWithTag(bottomDrawerTag)
+            .assertTopPositionInRootIsEqualTo(height)
+    }
+
+    @Test
     @LargeTest
     fun bottomDrawer_noDismissActionWhenClosed_hasDissmissActionWhenOpen(): Unit = runBlocking {
         lateinit var drawerState: BottomDrawerState
@@ -534,31 +716,113 @@
             BottomDrawer(
                 drawerState = drawerState,
                 drawerContent = {
-                    Box(Modifier.fillMaxSize().testTag("drawer"))
+                    Box(Modifier.fillMaxSize().testTag(bottomDrawerTag))
                 },
                 content = {}
             )
         }
 
         // Drawer should start in closed state and have no dismiss action
-        rule.onNodeWithTag("drawer", useUnmergedTree = true)
+        assertThat(drawerState.currentValue).isEqualTo(BottomDrawerValue.Closed)
+        rule.onNodeWithTag(bottomDrawerTag, useUnmergedTree = true)
             .onParent()
             .assert(SemanticsMatcher.keyNotDefined(SemanticsActions.Dismiss))
 
-        // When the drawer state is set to Opened
+        // When the drawer state is set to Open or Expanded
         drawerState.open()
+        assertThat(drawerState.currentValue)
+            .isAnyOf(BottomDrawerValue.Open, BottomDrawerValue.Expanded)
         // Then the drawer should be opened and have dismiss action
-        rule.onNodeWithTag("drawer", useUnmergedTree = true)
+        rule.onNodeWithTag(bottomDrawerTag, useUnmergedTree = true)
             .onParent()
             .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.Dismiss))
 
         // When the drawer state is set to Closed using dismiss action
-        rule.onNodeWithTag("drawer", useUnmergedTree = true)
+        rule.onNodeWithTag(bottomDrawerTag, useUnmergedTree = true)
             .onParent()
             .performSemanticsAction(SemanticsActions.Dismiss)
         // Then the drawer should be closed and have no dismiss action
-        rule.onNodeWithTag("drawer", useUnmergedTree = true)
+        rule.onNodeWithTag(bottomDrawerTag, useUnmergedTree = true)
             .onParent()
             .assert(SemanticsMatcher.keyNotDefined(SemanticsActions.Dismiss))
     }
+
+    @Test
+    @LargeTest
+    fun bottomDrawer_openAndClose_shortDrawer(): Unit = runBlocking {
+        lateinit var drawerState: BottomDrawerState
+        rule.setMaterialContent {
+            drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
+            BottomDrawer(
+                drawerState = drawerState,
+                drawerContent = {
+                    Box(Modifier.height(shortBottomDrawerHeight).testTag(bottomDrawerTag))
+                },
+                content = {}
+            )
+        }
+
+        val height = rule.rootHeight()
+        val topWhenOpened = height - shortBottomDrawerHeight
+        val topWhenExpanded = topWhenOpened
+        val topWhenClosed = height
+
+        // Drawer should start in closed state
+        rule.onNodeWithTag(bottomDrawerTag).assertTopPositionInRootIsEqualTo(topWhenClosed)
+
+        // When the drawer state is set to Opened
+        drawerState.open()
+        // Then the drawer should be opened
+        rule.onNodeWithTag(bottomDrawerTag).assertTopPositionInRootIsEqualTo(topWhenOpened)
+
+        // When the drawer state is set to Expanded
+        drawerState.expand()
+        // Then the drawer should be expanded
+        rule.onNodeWithTag(bottomDrawerTag).assertTopPositionInRootIsEqualTo(topWhenExpanded)
+
+        // When the drawer state is set to Closed
+        drawerState.close()
+        // Then the drawer should be closed
+        rule.onNodeWithTag(bottomDrawerTag).assertTopPositionInRootIsEqualTo(topWhenClosed)
+    }
+
+    @Test
+    @LargeTest
+    fun bottomDrawer_openAndClose_tallDrawer(): Unit = runBlocking {
+        lateinit var drawerState: BottomDrawerState
+        rule.setMaterialContent {
+            drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
+            BottomDrawer(
+                drawerState = drawerState,
+                drawerContent = {
+                    Box(Modifier.fillMaxSize().testTag(bottomDrawerTag))
+                },
+                content = {}
+            )
+        }
+
+        val width = rule.rootWidth()
+        val height = rule.rootHeight()
+        val topWhenOpened = if (width > height) 0.dp else (height / 2)
+        val topWhenExpanded = 0.dp
+        val topWhenClosed = height
+
+        // Drawer should start in closed state
+        rule.onNodeWithTag(bottomDrawerTag).assertTopPositionInRootIsEqualTo(topWhenClosed)
+
+        // When the drawer state is set to Opened
+        drawerState.open()
+        // Then the drawer should be opened
+        rule.onNodeWithTag(bottomDrawerTag).assertTopPositionInRootIsEqualTo(topWhenOpened)
+
+        // When the drawer state is set to Expanded
+        drawerState.expand()
+        // Then the drawer should be expanded
+        rule.onNodeWithTag(bottomDrawerTag).assertTopPositionInRootIsEqualTo(topWhenExpanded)
+
+        // When the drawer state is set to Closed
+        drawerState.close()
+        // Then the drawer should be closed
+        rule.onNodeWithTag(bottomDrawerTag).assertTopPositionInRootIsEqualTo(topWhenClosed)
+    }
 }
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt
index b152c14..f57d4f0 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/TabTest.kt
@@ -43,6 +43,7 @@
 import androidx.compose.ui.test.assertCountEquals
 import androidx.compose.ui.test.assertHasClickAction
 import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsEnabled
 import androidx.compose.ui.test.assertIsNotEnabled
 import androidx.compose.ui.test.assertIsNotSelected
@@ -618,6 +619,33 @@
     }
 
     @Test
+    fun scrollableTabRow_offScreenTabInitiallySelected() {
+        rule
+            .setMaterialContent {
+                var state by remember { mutableStateOf(9) }
+                val titles = List(10) { "Tab ${it + 1}" }
+                ScrollableTabRow(selectedTabIndex = state) {
+                    titles.forEachIndexed { index, title ->
+                        Tab(
+                            text = { Text(title) },
+                            selected = state == index,
+                            onClick = { state = index }
+                        )
+                    }
+                }
+            }
+
+        rule.onAllNodes(isSelectable())
+            .assertCountEquals(10)
+            .apply {
+                // The last tab should be selected and displayed (scrolled to)
+                get(9)
+                    .assertIsSelected()
+                    .assertIsDisplayed()
+            }
+    }
+
+    @Test
     fun scrollableTabRow_selectNewTab() {
         rule
             .setMaterialContent {
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 48389f1..e0455ee 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
@@ -17,6 +17,7 @@
 package androidx.compose.material
 
 import androidx.compose.animation.core.TweenSpec
+import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.foundation.Canvas
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.detectTapGestures
@@ -30,15 +31,19 @@
 import androidx.compose.foundation.layout.sizeIn
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.saveable.Saver
 import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.semantics.dismiss
@@ -48,9 +53,9 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.util.lerp
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.launch
+import kotlin.math.max
 import kotlin.math.roundToInt
 
 /**
@@ -164,10 +169,10 @@
     confirmStateChange = confirmStateChange
 ) {
     /**
-     * Whether the drawer is open.
+     * Whether the drawer is open, either in opened or expanded state.
      */
     val isOpen: Boolean
-        get() = currentValue == BottomDrawerValue.Open
+        get() = currentValue != BottomDrawerValue.Closed
 
     /**
      * Whether the drawer is closed.
@@ -183,19 +188,24 @@
 
     /**
      * Open the drawer with animation and suspend until it if fully opened or animation has been
-     * cancelled. This method will throw [CancellationException] if the animation is
-     * interrupted
+     * cancelled. If the content height is less than [BottomDrawerOpenFraction], the drawer state
+     * will move to [BottomDrawerValue.Expanded] instead.
      *
-     * @return the reason the open animation ended
+     * @throws [CancellationException] if the animation is interrupted
+     *
      */
-    suspend fun open() = animateTo(BottomDrawerValue.Open)
+    suspend fun open() {
+        val targetValue =
+            if (isOpenEnabled) BottomDrawerValue.Open else BottomDrawerValue.Expanded
+        animateTo(targetValue)
+    }
 
     /**
      * Close the drawer with animation and suspend until it if fully closed or animation has been
-     * cancelled. This method will throw [CancellationException] if the animation is
-     * interrupted
+     * cancelled.
      *
-     * @return the reason the close animation ended
+     * @throws [CancellationException] if the animation is interrupted
+     *
      */
     suspend fun close() = animateTo(BottomDrawerValue.Closed)
 
@@ -203,10 +213,14 @@
      * Expand the drawer with animation and suspend until it if fully expanded or animation has
      * been cancelled.
      *
-     * @return the reason the expand animation ended
+     * @throws [CancellationException] if the animation is interrupted
+     *
      */
     suspend fun expand() = animateTo(BottomDrawerValue.Expanded)
 
+    private val isOpenEnabled: Boolean
+        get() = anchors.values.contains(BottomDrawerValue.Open)
+
     internal val nestedScrollConnection = this.PreUpPostDownNestedScrollConnection
 
     companion object {
@@ -343,14 +357,14 @@
                             maxHeight = modalDrawerConstraints.maxHeight.toDp()
                         )
                 }
+                    .offset { IntOffset(drawerState.offset.value.roundToInt(), 0) }
+                    .padding(end = EndDrawerPadding)
                     .semantics {
                         paneTitle = Strings.NavigationMenu
                         if (drawerState.isOpen) {
                             dismiss(action = { scope.launch { drawerState.close() }; true })
                         }
-                    }
-                    .offset { IntOffset(drawerState.offset.value.roundToInt(), 0) }
-                    .padding(end = EndDrawerPadding),
+                    },
                 shape = drawerShape,
                 color = drawerBackgroundColor,
                 contentColor = drawerContentColor,
@@ -377,7 +391,7 @@
  * @sample androidx.compose.material.samples.BottomDrawerSample
  *
  * @param drawerState state of the drawer
- * @param modifier optional modifier for the drawer
+ * @param modifier optional [Modifier] for the entire component
  * @param gesturesEnabled whether or not drawer can be interacted by gestures
  * @param drawerShape shape of the drawer sheet
  * @param drawerElevation drawer sheet elevation. This controls the size of the shadow below the
@@ -390,7 +404,6 @@
  * @param scrimColor color of the scrim that obscures content when the drawer is open
  * @param content content of the rest of the UI
  *
- * @throws IllegalStateException when parent has [Float.POSITIVE_INFINITY] height
  */
 @Composable
 @ExperimentalMaterialApi
@@ -407,85 +420,77 @@
     content: @Composable () -> Unit
 ) {
     val scope = rememberCoroutineScope()
+
     BoxWithConstraints(modifier.fillMaxSize()) {
-        val modalDrawerConstraints = constraints
-        // TODO : think about Infinite max bounds case
-        if (!modalDrawerConstraints.hasBoundedHeight) {
-            throw IllegalStateException("Drawer shouldn't have infinite height")
+        val fullHeight = constraints.maxHeight.toFloat()
+        var drawerHeight by remember(fullHeight) { mutableStateOf(fullHeight) }
+        // TODO(b/178630869) Proper landscape support
+        val isLandscape = constraints.maxWidth > constraints.maxHeight
+
+        val minHeight = 0f
+        val peekHeight = fullHeight * BottomDrawerOpenFraction
+        val expandedHeight = max(minHeight, fullHeight - drawerHeight)
+        val anchors = if (drawerHeight < peekHeight || isLandscape) {
+            mapOf(
+                fullHeight to BottomDrawerValue.Closed,
+                expandedHeight to BottomDrawerValue.Expanded
+            )
+        } else {
+            mapOf(
+                fullHeight to BottomDrawerValue.Closed,
+                peekHeight to BottomDrawerValue.Open,
+                expandedHeight to BottomDrawerValue.Expanded
+            )
         }
 
-        val minValue = 0f
-        val maxValue = modalDrawerConstraints.maxHeight.toFloat()
-
-        // TODO: add proper landscape support
-        val isLandscape = modalDrawerConstraints.maxWidth > modalDrawerConstraints.maxHeight
-        val openValue = if (isLandscape) minValue else lerp(
-            minValue,
-            maxValue,
-            BottomDrawerOpenFraction
-        )
-        val anchors =
-            if (isLandscape) {
-                mapOf(
-                    maxValue to BottomDrawerValue.Closed,
-                    minValue to BottomDrawerValue.Open
-                )
-            } else {
-                mapOf(
-                    maxValue to BottomDrawerValue.Closed,
-                    openValue to BottomDrawerValue.Open,
-                    minValue to BottomDrawerValue.Expanded
-                )
-            }
         val blockClicks = if (drawerState.isClosed) {
             Modifier
         } else {
             Modifier.pointerInput(Unit) { detectTapGestures {} }
         }
-        Box(
+        val drawerConstraints = with(LocalDensity.current) {
             Modifier
-                .nestedScroll(drawerState.nestedScrollConnection)
-                .swipeable(
-                    state = drawerState,
-                    anchors = anchors,
-                    orientation = Orientation.Vertical,
-                    enabled = gesturesEnabled,
-                    resistance = null
+                .sizeIn(
+                    maxWidth = constraints.maxWidth.toDp(),
+                    maxHeight = constraints.maxHeight.toDp()
                 )
-        ) {
-            Box {
-                content()
-            }
-            Scrim(
-                open = drawerState.isOpen,
-                onClose = { scope.launch { drawerState.close() } },
-                fraction = {
-                    // as we scroll "from height to 0" , need to reverse fraction
-                    1 - calculateFraction(openValue, maxValue, drawerState.offset.value)
-                },
-                color = scrimColor
+        }
+        val swipeable = Modifier
+            .nestedScroll(drawerState.nestedScrollConnection)
+            .swipeable(
+                state = drawerState,
+                anchors = anchors,
+                orientation = Orientation.Vertical,
+                enabled = gesturesEnabled,
+                resistance = null
+            )
+
+        Box(swipeable) {
+            content()
+            BottomDrawerScrim(
+                color = scrimColor,
+                onDismiss = { scope.launch { drawerState.close() } },
+                visible = drawerState.targetValue != BottomDrawerValue.Closed
             )
             Surface(
-                modifier = with(LocalDensity.current) {
-                    Modifier.sizeIn(
-                        minWidth = modalDrawerConstraints.minWidth.toDp(),
-                        minHeight = modalDrawerConstraints.minHeight.toDp(),
-                        maxWidth = modalDrawerConstraints.maxWidth.toDp(),
-                        maxHeight = modalDrawerConstraints.maxHeight.toDp()
-                    )
-                }
+                drawerConstraints
+                    .onGloballyPositioned { position ->
+                        drawerHeight = position.size.height.toFloat()
+                    }
+                    .offset { IntOffset(x = 0, y = drawerState.offset.value.roundToInt()) }
                     .semantics {
                         paneTitle = Strings.NavigationMenu
                         if (drawerState.isOpen) {
+                            // TODO(b/180101663) The action currently doesn't return the correct results
                             dismiss(action = { scope.launch { drawerState.close() }; true })
                         }
-                    }.offset { IntOffset(0, drawerState.offset.value.roundToInt()) },
+                    },
                 shape = drawerShape,
                 color = drawerBackgroundColor,
                 contentColor = drawerContentColor,
                 elevation = drawerElevation
             ) {
-                Column(Modifier.fillMaxSize().then(blockClicks), content = drawerContent)
+                Column(blockClicks, content = drawerContent)
             }
         }
     }
@@ -515,6 +520,35 @@
     ((pos - a) / (b - a)).coerceIn(0f, 1f)
 
 @Composable
+private fun BottomDrawerScrim(
+    color: Color,
+    onDismiss: () -> Unit,
+    visible: Boolean
+) {
+    if (color != Color.Transparent) {
+        val alpha by animateFloatAsState(
+            targetValue = if (visible) 1f else 0f,
+            animationSpec = TweenSpec()
+        )
+        val dismissModifier = if (visible) {
+            Modifier.pointerInput(onDismiss) {
+                detectTapGestures { onDismiss() }
+            }
+        } else {
+            Modifier
+        }
+
+        Canvas(
+            Modifier
+                .fillMaxSize()
+                .then(dismissModifier)
+        ) {
+            drawRect(color = color, alpha = alpha)
+        }
+    }
+}
+
+@Composable
 private fun Scrim(
     open: Boolean,
     onClose: () -> Unit,
@@ -543,4 +577,4 @@
 // this is taken from the DrawerLayout's DragViewHelper as a min duration.
 private val AnimationSpec = TweenSpec<Float>(durationMillis = 256)
 
-internal const val BottomDrawerOpenFraction = 0.5f
+private const val BottomDrawerOpenFraction = 0.5f
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 6002125..e3ca819 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
@@ -104,6 +104,8 @@
     /**
      * Show the bottom sheet with animation and suspend until it's shown. If half expand is
      * enabled, the bottom sheet will be half expanded. Otherwise it will be fully expanded.
+     *
+     * @throws [CancellationException] if the animation is interrupted
      */
     suspend fun show() {
         val targetValue =
@@ -116,7 +118,7 @@
      * Half expand the bottom sheet if half expand is enabled with animation and suspend until it
      * animation is complete or cancelled
      *
-     * @return the reason the half expand animation ended
+     * @throws [CancellationException] if the animation is interrupted
      */
     internal suspend fun halfExpand() {
         if (!isHalfExpandedEnabled) {
@@ -127,19 +129,17 @@
 
     /**
      * Fully expand the bottom sheet with animation and suspend until it if fully expanded or
-     * animation has been cancelled. This method will throw [CancellationException] if the
-     * animation is interrupted
-     *
-     * @return the reason the expand animation ended
+     * animation has been cancelled.
+     * *
+     * @throws [CancellationException] if the animation is interrupted
      */
     internal suspend fun expand() = animateTo(ModalBottomSheetValue.Expanded)
 
     /**
      * Hide the bottom sheet with animation and suspend until it if fully hidden or animation has
-     * been cancelled. This method will throw [CancellationException] if the animation is
-     * interrupted
+     * been cancelled.
      *
-     * @return the reason the hide animation ended
+     * @throws [CancellationException] if the animation is interrupted
      */
     suspend fun hide() = animateTo(ModalBottomSheetValue.Hidden)
 
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt
index 9b4b9e4..2819fda 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Scaffold.kt
@@ -16,18 +16,13 @@
 
 package androidx.compose.material
 
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.AnimationVector1D
-import androidx.compose.animation.core.TweenSpec
 import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.Stable
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.staticCompositionLocalOf
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
@@ -39,7 +34,6 @@
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastMap
 import androidx.compose.ui.util.fastMaxBy
-import kotlinx.coroutines.launch
 
 /**
  * State for [Scaffold] composable component.
@@ -227,9 +221,6 @@
     fab: @Composable () -> Unit,
     bottomBar: @Composable () -> Unit
 ) {
-    val fabAnimatableState =
-        remember { mutableStateOf<Animatable<Float, AnimationVector1D>?>(null) }
-    val scope = rememberCoroutineScope()
     SubcomposeLayout { constraints ->
         val layoutWidth = constraints.maxWidth
         val layoutHeight = constraints.maxHeight
@@ -271,24 +262,10 @@
                 0
             }
 
-            val floatOffset = fabLeftOffset.toFloat()
-
-            val fabAnimatable = fabAnimatableState.value ?: Animatable(floatOffset).also {
-                fabAnimatableState.value = it
-            }
-            if (fabAnimatable.targetValue != floatOffset) {
-                scope.launch {
-                    fabAnimatable.animateTo(
-                        targetValue = floatOffset,
-                        animationSpec = FabPositionAnimationSpec
-                    )
-                }
-            }
-
             val fabPlacement = if (fabWidth != 0 && fabHeight != 0) {
                 FabPlacement(
                     isDocked = isFabDocked,
-                    left = fabAnimatable.value.toInt(),
+                    left = fabLeftOffset,
                     width = fabWidth,
                     height = fabHeight
                 )
@@ -356,7 +333,7 @@
             }
             // Explicitly not using placeRelative here as `leftOffset` already accounts for RTL
             fabPlaceables.fastForEach {
-                it.place(fabAnimatable.value.toInt(), layoutHeight - fabOffsetFromBottom)
+                it.place(fabLeftOffset, layoutHeight - fabOffsetFromBottom)
             }
         }
     }
@@ -389,5 +366,3 @@
 private val FabSpacing = 16.dp
 
 private enum class ScaffoldLayoutContent { TopBar, MainContent, Snackbar, Fab, BottomBar }
-
-private val FabPositionAnimationSpec = TweenSpec<Float>(durationMillis = 200)
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TabRow.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TabRow.kt
index c44c731..604dd36 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TabRow.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/TabRow.kt
@@ -227,10 +227,9 @@
     ) {
         val scrollState = rememberScrollState()
         val coroutineScope = rememberCoroutineScope()
-        val scrollableTabData = remember(scrollState) {
+        val scrollableTabData = remember(scrollState, coroutineScope) {
             ScrollableTabData(
                 scrollState = scrollState,
-                selectedTab = selectedTabIndex,
                 coroutineScope = coroutineScope
             )
         }
@@ -429,15 +428,18 @@
  */
 private class ScrollableTabData(
     private val scrollState: ScrollState,
-    private var selectedTab: Int,
     private val coroutineScope: CoroutineScope
 ) {
+    private var selectedTab: Int? = null
+
     fun onLaidOut(
         density: Density,
         edgeOffset: Int,
         tabPositions: List<TabPosition>,
         selectedTab: Int
     ) {
+        // Animate if the new tab is different from the old tab, or this is called for the first
+        // time (i.e selectedTab is `null`).
         if (this.selectedTab != selectedTab) {
             this.selectedTab = selectedTab
             tabPositions.getOrNull(selectedTab)?.let {
@@ -465,7 +467,7 @@
         tabPositions: List<TabPosition>
     ): Int = with(density) {
         val totalTabRowWidth = tabPositions.last().right.roundToPx() + edgeOffset
-        val visibleWidth = totalTabRowWidth - scrollState.maxValue.toInt()
+        val visibleWidth = totalTabRowWidth - scrollState.maxValue
         val tabOffset = left.roundToPx()
         val scrollerCenter = visibleWidth / 2
         val tabWidth = width.roundToPx()
diff --git a/compose/runtime/runtime-lint/build.gradle b/compose/runtime/runtime-lint/build.gradle
index aa6a9b5..684899c 100644
--- a/compose/runtime/runtime-lint/build.gradle
+++ b/compose/runtime/runtime-lint/build.gradle
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+import androidx.build.BundleInsideHelper
 import androidx.build.LibraryGroups
 import androidx.build.LibraryType
 
@@ -24,6 +25,8 @@
     id("kotlin")
 }
 
+BundleInsideHelper.forInsideLintJar(project)
+
 dependencies {
     // compileOnly because we use lintChecks and it doesn't allow other types of deps
     // this ugly hack exists because of b/63873667
@@ -33,6 +36,7 @@
         compileOnly(LINT_API_MIN)
     }
     compileOnly(KOTLIN_STDLIB)
+    bundleInside(project(":compose:lint:common"))
 
     testImplementation(KOTLIN_STDLIB)
     testImplementation(LINT_CORE)
diff --git a/compose/runtime/runtime-lint/lint-baseline.xml b/compose/runtime/runtime-lint/lint-baseline.xml
deleted file mode 100644
index 297ae16..0000000
--- a/compose/runtime/runtime-lint/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" version="4.2.0-beta02">
-
-</issues>
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableCoroutineCreationDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableCoroutineCreationDetector.kt
new file mode 100644
index 0000000..28e6b4b
--- /dev/null
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableCoroutineCreationDetector.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.compose.runtime.lint
+
+import androidx.compose.lint.Name
+import androidx.compose.lint.Package
+import androidx.compose.lint.isInPackageName
+import androidx.compose.lint.isInvokedWithinComposable
+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 com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+import java.util.EnumSet
+
+/**
+ * [Detector] that checks `async` and `launch` calls to make sure they don't happen inside the
+ * body of a composable function / lambda.
+ */
+class ComposableCoroutineCreationDetector : Detector(), SourceCodeScanner {
+    override fun getApplicableMethodNames() = listOf(Async.shortName, Launch.shortName)
+
+    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+        if (!method.isInPackageName(CoroutinePackageName)) return
+
+        if (node.isInvokedWithinComposable()) {
+            context.report(
+                CoroutineCreationDuringComposition,
+                node,
+                context.getNameLocation(node),
+                "Calls to ${method.name} should happen inside a LaunchedEffect and " +
+                    "not composition"
+            )
+        }
+    }
+
+    companion object {
+        val CoroutineCreationDuringComposition = Issue.create(
+            "CoroutineCreationDuringComposition",
+            "Calls to `async` or `launch` should happen inside a LaunchedEffect and not " +
+                "composition",
+            "Creating a coroutine with `async` or `launch` during composition is often incorrect " +
+                "- this means that a coroutine will be created even if the composition fails / is" +
+                " rolled back, and it also means that multiple coroutines could end up mutating " +
+                "the same state, causing inconsistent results. Instead, use `LaunchedEffect` and " +
+                "create coroutines inside the suspending block. The block will only run after a " +
+                "successful composition, and will cancel existing coroutines when `key` changes, " +
+                "allowing correct cleanup.",
+            Category.CORRECTNESS, 3, Severity.ERROR,
+            Implementation(
+                ComposableCoroutineCreationDetector::class.java,
+                EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
+            )
+        )
+    }
+}
+
+private val CoroutinePackageName = Package("kotlinx.coroutines")
+private val Async = Name(CoroutinePackageName, "async")
+private val Launch = Name(CoroutinePackageName, "launch")
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetector.kt
index 4e93399..ad32a95 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetector.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetector.kt
@@ -18,6 +18,8 @@
 
 package androidx.compose.runtime.lint
 
+import androidx.compose.lint.isComposable
+import androidx.compose.lint.returnsUnit
 import com.android.tools.lint.client.api.UElementHandler
 import com.android.tools.lint.detector.api.Category
 import com.android.tools.lint.detector.api.Detector
@@ -28,15 +30,12 @@
 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 com.intellij.psi.PsiType
 import org.jetbrains.kotlin.psi.KtFunctionType
 import org.jetbrains.kotlin.psi.KtNullableType
 import org.jetbrains.kotlin.psi.KtParameter
-import org.jetbrains.uast.UAnnotation
 import org.jetbrains.uast.UElement
 import org.jetbrains.uast.UMethod
 import org.jetbrains.uast.UParameter
-import org.jetbrains.uast.toUElement
 import java.util.EnumSet
 
 /**
@@ -56,7 +55,7 @@
             if (!node.isComposable) return
 
             // Ignore non-unit composable functions
-            if (node.returnType != PsiType.VOID) return
+            if (!node.returnsUnit) return
 
             /**
              * Small class to hold information from lambda properties needed for lint checks.
@@ -72,21 +71,15 @@
                 // an extension function - just ignore it.
                 val ktParameter = parameter.sourcePsi as? KtParameter ?: return@mapNotNull null
 
-                val typeReference = ktParameter.typeReference!!
+                val isComposable = parameter.isComposable
 
-                // Ideally this annotation should be available on the PsiType itself
-                // https://youtrack.jetbrains.com/issue/KT-45244
-                val hasComposableAnnotationOnType = typeReference.annotationEntries.any {
-                    (it.toUElement() as UAnnotation).qualifiedName == ComposableFqn
-                }
-
-                val functionType = when (val typeElement = typeReference.typeElement) {
-                    is KtFunctionType -> typeElement
-                    is KtNullableType -> typeElement.innerType as? KtFunctionType
+                val functionType = when (val type = ktParameter.typeReference!!.typeElement) {
+                    is KtFunctionType -> type
+                    is KtNullableType -> type.innerType as? KtFunctionType
                     else -> null
                 }
 
-                if (functionType != null && hasComposableAnnotationOnType) {
+                if (functionType != null && isComposable) {
                     ComposableLambdaParameterInfo(parameter, functionType)
                 } else {
                     null
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableNamingDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableNamingDetector.kt
index e4e905b..b45d87b 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableNamingDetector.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/ComposableNamingDetector.kt
@@ -18,6 +18,8 @@
 
 package androidx.compose.runtime.lint
 
+import androidx.compose.lint.isComposable
+import androidx.compose.lint.returnsUnit
 import com.android.tools.lint.client.api.UElementHandler
 import com.android.tools.lint.detector.api.Category
 import com.android.tools.lint.detector.api.Detector
@@ -28,7 +30,6 @@
 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 com.intellij.psi.PsiType
 import org.jetbrains.uast.UMethod
 import java.util.EnumSet
 import java.util.Locale
@@ -107,5 +108,3 @@
         )
     }
 }
-
-private val UMethod.returnsUnit get() = returnType == PsiType.VOID
\ No newline at end of file
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/CompositionLocalNamingDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/CompositionLocalNamingDetector.kt
index f9250bc..74d6f2c 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/CompositionLocalNamingDetector.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/CompositionLocalNamingDetector.kt
@@ -18,6 +18,7 @@
 
 package androidx.compose.runtime.lint
 
+import androidx.compose.lint.Names
 import com.android.tools.lint.client.api.UElementHandler
 import com.android.tools.lint.detector.api.Category
 import com.android.tools.lint.detector.api.Detector
@@ -53,10 +54,10 @@
             if ((node.sourcePsi as? KtProperty)?.isLocal == true) return
 
             val type = node.type
-            if (!InheritanceUtil.isInheritor(type, CompositionLocalFqn)) return
+            if (!InheritanceUtil.isInheritor(type, Names.Runtime.CompositionLocal.javaFqn)) return
 
             val name = node.name
-            if (name!!.startsWith(CompositionLocalPrefix, ignoreCase = true)) return
+            if (name!!.startsWith("Local", ignoreCase = true)) return
 
             // Kotlinc can't disambiguate overloads for report / getNameLocation otherwise
             val uElementNode: UElement = node
@@ -86,6 +87,3 @@
         )
     }
 }
-
-private const val CompositionLocalFqn = "androidx.compose.runtime.CompositionLocal"
-private const val CompositionLocalPrefix = "Local"
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RememberDetector.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RememberDetector.kt
index 74ff784e..966e6ea 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RememberDetector.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RememberDetector.kt
@@ -18,6 +18,8 @@
 
 package androidx.compose.runtime.lint
 
+import androidx.compose.lint.Names
+import androidx.compose.lint.isInPackageName
 import com.android.tools.lint.detector.api.Category
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Implementation
@@ -26,7 +28,6 @@
 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 com.intellij.psi.PsiJavaFile
 import com.intellij.psi.PsiMethod
 import com.intellij.psi.PsiType
 import org.jetbrains.uast.UCallExpression
@@ -36,10 +37,10 @@
  * [Detector] that checks `remember` calls to make sure they are not returning [Unit].
  */
 class RememberDetector : Detector(), SourceCodeScanner {
-    override fun getApplicableMethodNames(): List<String> = listOf(RememberShortName)
+    override fun getApplicableMethodNames(): List<String> = listOf(Names.Runtime.Remember.shortName)
 
     override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
-        if ((method.containingFile as? PsiJavaFile)?.packageName == RuntimePackageName) {
+        if (method.isInPackageName(Names.Runtime.PackageName)) {
             if (node.getExpressionType() == PsiType.VOID) {
                 context.report(
                     RememberReturnType,
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RuntimeIssueRegistry.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RuntimeIssueRegistry.kt
index a860b1a..b550b50 100644
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RuntimeIssueRegistry.kt
+++ b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/RuntimeIssueRegistry.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:Suppress("UnstableApiUsage")
+
 package androidx.compose.runtime.lint
 
 import com.android.tools.lint.client.api.IssueRegistry
@@ -27,10 +29,11 @@
     override val api = 8
     override val minApi = CURRENT_API
     override val issues get() = listOf(
-        CompositionLocalNamingDetector.CompositionLocalNaming,
+        ComposableCoroutineCreationDetector.CoroutineCreationDuringComposition,
         ComposableLambdaParameterDetector.ComposableLambdaParameterNaming,
         ComposableLambdaParameterDetector.ComposableLambdaParameterPosition,
         ComposableNamingDetector.ComposableNaming,
+        CompositionLocalNamingDetector.CompositionLocalNaming,
         RememberDetector.RememberReturnType
     )
 }
diff --git a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/Utils.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/Utils.kt
deleted file mode 100644
index 6d8f4d6..0000000
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/Utils.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.runtime.lint
-
-import org.jetbrains.uast.UMethod
-
-// TODO: KotlinUMethodWithFakeLightDelegate.hasAnnotation() returns null for some reason, so just
-// look at the annotations directly
-// TODO: annotations is deprecated but the replacement uAnnotations isn't available on the
-// version of lint / uast we compile against
-@Suppress("DEPRECATION")
-val UMethod.isComposable get() = annotations.any { it.qualifiedName == ComposableFqn }
-
-const val RuntimePackageName = "androidx.compose.runtime"
-
-const val ComposableFqn = "$RuntimePackageName.Composable"
-val ComposableShortName = ComposableFqn.split(".").last()
-
-const val RememberShortName = "remember"
diff --git a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableCoroutineCreationDetectorTest.kt b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableCoroutineCreationDetectorTest.kt
new file mode 100644
index 0000000..c3a1f89
--- /dev/null
+++ b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableCoroutineCreationDetectorTest.kt
@@ -0,0 +1,346 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.compose.runtime.lint
+
+import androidx.compose.lint.Stubs
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/* ktlint-disable max-line-length */
+@RunWith(JUnit4::class)
+
+// TODO: add tests for methods defined in class files when we update Lint to support bytecode()
+//  test files
+
+/**
+ * Test for [ComposableCoroutineCreationDetector].
+ */
+class ComposableCoroutineCreationDetectorTest : LintDetectorTest() {
+    override fun getDetector(): Detector = ComposableCoroutineCreationDetector()
+
+    override fun getIssues(): MutableList<Issue> =
+        mutableListOf(ComposableCoroutineCreationDetector.CoroutineCreationDuringComposition)
+
+    private val coroutineBuildersStub: TestFile = kotlin(
+        """
+        package kotlinx.coroutines
+
+        object CoroutineScope
+
+        fun CoroutineScope.async(
+            block: suspend CoroutineScope.() -> Unit
+        ) {}
+
+        fun CoroutineScope.launch(
+            block: suspend CoroutineScope.() -> Unit
+        ) {}
+    """
+    )
+
+    @Test
+    fun errors() {
+        lint().files(
+            kotlin(
+                """
+                package androidx.compose.runtime.foo
+
+                import androidx.compose.runtime.Composable
+                import kotlinx.coroutines.*
+
+                @Composable
+                fun Test() {
+                    CoroutineScope.async {}
+                    CoroutineScope.launch {}
+                }
+
+                val lambda = @Composable {
+                    CoroutineScope.async {}
+                    CoroutineScope.launch {}
+                }
+
+                val lambda2: @Composable () -> Unit = {
+                    CoroutineScope.async {}
+                    CoroutineScope.launch {}
+                }
+
+                @Composable
+                fun LambdaParameter(content: @Composable () -> Unit) {}
+
+                @Composable
+                fun Test2() {
+                    LambdaParameter(content = {
+                        CoroutineScope.async {}
+                        CoroutineScope.launch {}
+                    })
+                    LambdaParameter {
+                        CoroutineScope.async {}
+                        CoroutineScope.launch {}
+                    }
+                }
+
+                fun test3() {
+                    val localLambda1 = @Composable {
+                        CoroutineScope.async {}
+                        CoroutineScope.launch {}
+                    }
+
+                    val localLambda2: @Composable () -> Unit = {
+                        CoroutineScope.async {}
+                        CoroutineScope.launch {}
+                    }
+                }
+            """
+            ),
+            kotlin(Stubs.Composable),
+            coroutineBuildersStub
+        )
+            .run()
+            .expect(
+                """
+src/androidx/compose/runtime/foo/test.kt:9: Error: Calls to async should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                    CoroutineScope.async {}
+                                   ~~~~~
+src/androidx/compose/runtime/foo/test.kt:10: Error: Calls to launch should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                    CoroutineScope.launch {}
+                                   ~~~~~~
+src/androidx/compose/runtime/foo/test.kt:14: Error: Calls to async should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                    CoroutineScope.async {}
+                                   ~~~~~
+src/androidx/compose/runtime/foo/test.kt:15: Error: Calls to launch should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                    CoroutineScope.launch {}
+                                   ~~~~~~
+src/androidx/compose/runtime/foo/test.kt:19: Error: Calls to async should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                    CoroutineScope.async {}
+                                   ~~~~~
+src/androidx/compose/runtime/foo/test.kt:20: Error: Calls to launch should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                    CoroutineScope.launch {}
+                                   ~~~~~~
+src/androidx/compose/runtime/foo/test.kt:29: Error: Calls to async should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                        CoroutineScope.async {}
+                                       ~~~~~
+src/androidx/compose/runtime/foo/test.kt:30: Error: Calls to launch should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                        CoroutineScope.launch {}
+                                       ~~~~~~
+src/androidx/compose/runtime/foo/test.kt:33: Error: Calls to async should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                        CoroutineScope.async {}
+                                       ~~~~~
+src/androidx/compose/runtime/foo/test.kt:34: Error: Calls to launch should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                        CoroutineScope.launch {}
+                                       ~~~~~~
+src/androidx/compose/runtime/foo/test.kt:40: Error: Calls to async should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                        CoroutineScope.async {}
+                                       ~~~~~
+src/androidx/compose/runtime/foo/test.kt:41: Error: Calls to launch should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                        CoroutineScope.launch {}
+                                       ~~~~~~
+src/androidx/compose/runtime/foo/test.kt:45: Error: Calls to async should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                        CoroutineScope.async {}
+                                       ~~~~~
+src/androidx/compose/runtime/foo/test.kt:46: Error: Calls to launch should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                        CoroutineScope.launch {}
+                                       ~~~~~~
+14 errors, 0 warnings
+            """
+            )
+    }
+
+    @Test
+    fun errors_inlineFunctions() {
+        lint().files(
+            kotlin(
+                """
+                package androidx.compose.runtime.foo
+
+                import androidx.compose.runtime.Composable
+                import kotlinx.coroutines.*
+
+                @Composable
+                fun Test() {
+                    run {
+                        CoroutineScope.async {}
+                        CoroutineScope.launch {}
+                    }
+                }
+
+                val lambda = @Composable {
+                    run {
+                        CoroutineScope.async {}
+                        CoroutineScope.launch {}
+                    }
+                }
+
+                val lambda2: @Composable () -> Unit = {
+                    run {
+                        CoroutineScope.async {}
+                        CoroutineScope.launch {}
+                    }
+                }
+
+                @Composable
+                fun LambdaParameter(content: @Composable () -> Unit) {}
+
+                @Composable
+                fun Test2() {
+                    LambdaParameter(content = {
+                        run {
+                            CoroutineScope.async {}
+                            CoroutineScope.launch {}
+                        }
+                    })
+                    LambdaParameter {
+                        run {
+                            CoroutineScope.async {}
+                            CoroutineScope.launch {}
+                        }
+                    }
+                }
+
+                fun test3() {
+                    val localLambda1 = @Composable {
+                        run {
+                            CoroutineScope.async {}
+                            CoroutineScope.launch {}
+                        }
+                    }
+
+                    val localLambda2: @Composable () -> Unit = {
+                        run {
+                            CoroutineScope.async {}
+                            CoroutineScope.launch {}
+                        }
+                    }
+                }
+            """
+            ),
+            kotlin(Stubs.Composable),
+            coroutineBuildersStub
+        )
+            .run()
+            .expect(
+                """
+src/androidx/compose/runtime/foo/test.kt:10: Error: Calls to async should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                        CoroutineScope.async {}
+                                       ~~~~~
+src/androidx/compose/runtime/foo/test.kt:11: Error: Calls to launch should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                        CoroutineScope.launch {}
+                                       ~~~~~~
+src/androidx/compose/runtime/foo/test.kt:17: Error: Calls to async should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                        CoroutineScope.async {}
+                                       ~~~~~
+src/androidx/compose/runtime/foo/test.kt:18: Error: Calls to launch should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                        CoroutineScope.launch {}
+                                       ~~~~~~
+src/androidx/compose/runtime/foo/test.kt:24: Error: Calls to async should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                        CoroutineScope.async {}
+                                       ~~~~~
+src/androidx/compose/runtime/foo/test.kt:25: Error: Calls to launch should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                        CoroutineScope.launch {}
+                                       ~~~~~~
+src/androidx/compose/runtime/foo/test.kt:36: Error: Calls to async should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                            CoroutineScope.async {}
+                                           ~~~~~
+src/androidx/compose/runtime/foo/test.kt:37: Error: Calls to launch should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                            CoroutineScope.launch {}
+                                           ~~~~~~
+src/androidx/compose/runtime/foo/test.kt:42: Error: Calls to async should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                            CoroutineScope.async {}
+                                           ~~~~~
+src/androidx/compose/runtime/foo/test.kt:43: Error: Calls to launch should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                            CoroutineScope.launch {}
+                                           ~~~~~~
+src/androidx/compose/runtime/foo/test.kt:51: Error: Calls to async should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                            CoroutineScope.async {}
+                                           ~~~~~
+src/androidx/compose/runtime/foo/test.kt:52: Error: Calls to launch should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                            CoroutineScope.launch {}
+                                           ~~~~~~
+src/androidx/compose/runtime/foo/test.kt:58: Error: Calls to async should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                            CoroutineScope.async {}
+                                           ~~~~~
+src/androidx/compose/runtime/foo/test.kt:59: Error: Calls to launch should happen inside a LaunchedEffect and not composition [CoroutineCreationDuringComposition]
+                            CoroutineScope.launch {}
+                                           ~~~~~~
+14 errors, 0 warnings
+            """
+            )
+    }
+
+    @Test
+    fun noErrors() {
+        lint().files(
+            kotlin(
+                """
+                package androidx.compose.runtime.foo
+
+                import androidx.compose.runtime.Composable
+                import kotlinx.coroutines.*
+
+                fun test() {
+                    CoroutineScope.async {}
+                    CoroutineScope.launch {}
+                }
+
+                val lambda = {
+                    CoroutineScope.async {}
+                    CoroutineScope.launch {}
+                }
+
+                val lambda2: () -> Unit = {
+                    CoroutineScope.async {}
+                    CoroutineScope.launch {}
+                }
+
+                fun lambdaParameter(action: () -> Unit) {}
+
+                fun test2() {
+                    lambdaParameter(action = {
+                        CoroutineScope.async {}
+                        CoroutineScope.launch {}
+                    })
+                    lambdaParameter {
+                        CoroutineScope.async {}
+                        CoroutineScope.launch {}
+                    }
+                }
+
+                fun test3() {
+                    val localLambda1 = {
+                        CoroutineScope.async {}
+                        CoroutineScope.launch {}
+                    }
+
+                    val localLambda2: () -> Unit = {
+                        CoroutineScope.async {}
+                        CoroutineScope.launch {}
+                    }
+                }
+            """
+            ),
+            kotlin(Stubs.Composable),
+            coroutineBuildersStub
+        )
+            .run()
+            .expectClean()
+    }
+}
+/* ktlint-enable max-line-length */
diff --git a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetectorTest.kt b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetectorTest.kt
index 22f58cd..da08c10 100644
--- a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetectorTest.kt
+++ b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableLambdaParameterDetectorTest.kt
@@ -18,6 +18,7 @@
 
 package androidx.compose.runtime.lint
 
+import androidx.compose.lint.Stubs
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
@@ -54,7 +55,7 @@
                 }
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expect(
@@ -90,7 +91,7 @@
                 }
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expect(
@@ -118,7 +119,7 @@
                 }
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expect(
@@ -159,7 +160,7 @@
                 }
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expect(
@@ -195,7 +196,7 @@
                 }
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expectClean()
@@ -218,7 +219,7 @@
                 ) {}
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expectClean()
@@ -239,7 +240,7 @@
                 fun FooScope.Button(foo: Int) {}
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expectClean()
@@ -260,7 +261,7 @@
                 fun Button(foo: @Composable (Int, Boolean) -> Unit) {}
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expectClean()
@@ -281,7 +282,7 @@
                 }
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expectClean()
diff --git a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableNamingDetectorTest.kt b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableNamingDetectorTest.kt
index 4e033e6..c612554 100644
--- a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableNamingDetectorTest.kt
+++ b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/ComposableNamingDetectorTest.kt
@@ -18,6 +18,7 @@
 
 package androidx.compose.runtime.lint
 
+import androidx.compose.lint.Stubs
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
@@ -50,7 +51,7 @@
                 fun button() {}
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expect(
@@ -84,7 +85,7 @@
                 fun Button() {}
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expectClean()
@@ -103,7 +104,7 @@
                 fun getInt(): Int { return 5 }
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expectClean()
@@ -122,7 +123,7 @@
                 fun GetInt(): Int { return 5 }
             """
             ),
-            composableStub
+            kotlin(Stubs.Composable)
         )
             .run()
             .expect(
diff --git a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/RememberDetectorTest.kt b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/RememberDetectorTest.kt
index 06f8db1..df54bf8 100644
--- a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/RememberDetectorTest.kt
+++ b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/RememberDetectorTest.kt
@@ -18,6 +18,7 @@
 
 package androidx.compose.runtime.lint
 
+import androidx.compose.lint.Stubs
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
@@ -107,8 +108,8 @@
                 }
             """
             ),
-            composableStub,
-            rememberStub
+            kotlin(Stubs.Composable),
+            kotlin(Stubs.Remember)
         )
             .run()
             .expect(
@@ -218,8 +219,8 @@
                 }
             """
             ),
-            composableStub,
-            rememberStub
+            kotlin(Stubs.Composable),
+            kotlin(Stubs.Remember)
         )
             .run()
             .expect(
@@ -329,8 +330,8 @@
                 }
             """
             ),
-            composableStub,
-            rememberStub
+            kotlin(Stubs.Composable),
+            kotlin(Stubs.Remember)
         )
             .run()
             .expectClean()
diff --git a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/Stubs.kt b/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/Stubs.kt
deleted file mode 100644
index 9c086d2..0000000
--- a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/Stubs.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:Suppress("UnstableApiUsage")
-
-package androidx.compose.runtime.lint
-
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-
-val composableStub: LintDetectorTest.TestFile = LintDetectorTest.kotlin(
-    """
-        package androidx.compose.runtime
-
-        @MustBeDocumented
-        @Retention(AnnotationRetention.BINARY)
-        @Target(
-            AnnotationTarget.FUNCTION,
-            AnnotationTarget.TYPE,
-            AnnotationTarget.TYPE_PARAMETER,
-            AnnotationTarget.PROPERTY
-        )
-        annotation class Composable
-    """
-)
-
-val rememberStub: LintDetectorTest.TestFile = LintDetectorTest.kotlin(
-"""
-        package androidx.compose.runtime
-
-        import androidx.compose.runtime.Composable
-
-        @Composable
-        inline fun <T> remember(calculation: () -> T): T = calculation()
-
-        @Composable
-        inline fun <T, V1> remember(
-            v1: V1,
-            calculation: () -> T
-        ): T = calculation()
-
-        @Composable
-        inline fun <T, V1, V2> remember(
-            v1: V1,
-            v2: V2,
-            calculation: () -> T
-        ): T = calculation()
-
-        @Composable
-        inline fun <T, V1, V2, V3> remember(
-            v1: V1,
-            v2: V2,
-            v3: V3,
-            calculation: () -> T
-        ): T = calculation()
-
-        @Composable
-        inline fun <V> remember(
-            vararg inputs: Any?,
-            calculation: () -> V
-        ): V = calculation()
-    """
-)
diff --git a/compose/runtime/runtime-livedata/build.gradle b/compose/runtime/runtime-livedata/build.gradle
index 3254423..88b093c 100644
--- a/compose/runtime/runtime-livedata/build.gradle
+++ b/compose/runtime/runtime-livedata/build.gradle
@@ -15,7 +15,7 @@
  */
 
 import androidx.build.LibraryGroups
-import androidx.build.Publish
+import androidx.build.LibraryType
 
 import static androidx.build.dependencies.DependenciesKt.*
 
@@ -43,7 +43,7 @@
 
 androidx {
     name = "Compose LiveData integration"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.RUNTIME
     inceptionYear = "2020"
     description = "Compose integration with LiveData"
diff --git a/compose/runtime/runtime-livedata/lint-baseline.xml b/compose/runtime/runtime-livedata/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/runtime/runtime-livedata/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/runtime/runtime-livedata/samples/lint-baseline.xml b/compose/runtime/runtime-livedata/samples/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/runtime/runtime-livedata/samples/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/runtime/runtime-rxjava2/build.gradle b/compose/runtime/runtime-rxjava2/build.gradle
index 335c87f..3ec8592 100644
--- a/compose/runtime/runtime-rxjava2/build.gradle
+++ b/compose/runtime/runtime-rxjava2/build.gradle
@@ -15,7 +15,7 @@
  */
 
 import androidx.build.LibraryGroups
-import androidx.build.Publish
+import androidx.build.LibraryType
 
 import static androidx.build.dependencies.DependenciesKt.*
 
@@ -42,7 +42,7 @@
 
 androidx {
     name = "Compose RxJava 2 integration"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.RUNTIME
     inceptionYear = "2020"
     description = "Compose integration with RxJava 2"
diff --git a/compose/runtime/runtime-rxjava2/lint-baseline.xml b/compose/runtime/runtime-rxjava2/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/runtime/runtime-rxjava2/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/runtime/runtime-rxjava2/samples/lint-baseline.xml b/compose/runtime/runtime-rxjava2/samples/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/runtime/runtime-rxjava2/samples/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/runtime/runtime-rxjava3/build.gradle b/compose/runtime/runtime-rxjava3/build.gradle
index 5c079d6..949a855 100644
--- a/compose/runtime/runtime-rxjava3/build.gradle
+++ b/compose/runtime/runtime-rxjava3/build.gradle
@@ -15,7 +15,7 @@
  */
 
 import androidx.build.LibraryGroups
-import androidx.build.Publish
+import androidx.build.LibraryType
 
 import static androidx.build.dependencies.DependenciesKt.*
 
@@ -42,7 +42,7 @@
 
 androidx {
     name = "Compose RxJava 3 integration"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.RUNTIME
     inceptionYear = "2020"
     description = "Compose integration with RxJava 3"
diff --git a/compose/runtime/runtime-rxjava3/lint-baseline.xml b/compose/runtime/runtime-rxjava3/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/runtime/runtime-rxjava3/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/runtime/runtime-rxjava3/samples/lint-baseline.xml b/compose/runtime/runtime-rxjava3/samples/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/runtime/runtime-rxjava3/samples/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/runtime/runtime-saveable/build.gradle b/compose/runtime/runtime-saveable/build.gradle
index 692c9de..d062d34 100644
--- a/compose/runtime/runtime-saveable/build.gradle
+++ b/compose/runtime/runtime-saveable/build.gradle
@@ -17,7 +17,7 @@
 
 import androidx.build.AndroidXUiPlugin
 import androidx.build.LibraryGroups
-import androidx.build.Publish
+import androidx.build.LibraryType
 
 import static androidx.build.dependencies.DependenciesKt.*
 
@@ -112,7 +112,7 @@
 
 androidx {
     name = "Compose Saveable"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.RUNTIME
     inceptionYear = "2020"
     description = "Compose components that allow saving and restoring the local ui state"
diff --git a/compose/runtime/runtime-saveable/lint-baseline.xml b/compose/runtime/runtime-saveable/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/runtime/runtime-saveable/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/runtime/runtime-saveable/samples/lint-baseline.xml b/compose/runtime/runtime-saveable/samples/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/runtime/runtime-saveable/samples/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/runtime/runtime-saveable/src/androidAndroidTest/AndroidManifest.xml b/compose/runtime/runtime-saveable/src/androidAndroidTest/AndroidManifest.xml
index 391101b..4148f68 100644
--- a/compose/runtime/runtime-saveable/src/androidAndroidTest/AndroidManifest.xml
+++ b/compose/runtime/runtime-saveable/src/androidAndroidTest/AndroidManifest.xml
@@ -30,5 +30,6 @@
         <activity android:name="androidx.compose.runtime.saveable.RecreationTest6Activity" />
         <activity android:name="androidx.compose.runtime.saveable.RecreationTest7Activity" />
         <activity android:name="androidx.compose.runtime.saveable.RecreationTest8Activity" />
+        <activity android:name="androidx.compose.runtime.saveable.SaveableStateHolderTest$Activity" />
     </application>
 </manifest>
diff --git a/compose/runtime/runtime-saveable/src/androidAndroidTest/kotlin/androidx/compose/runtime/saveable/SaveableStateHolderTest.kt b/compose/runtime/runtime-saveable/src/androidAndroidTest/kotlin/androidx/compose/runtime/saveable/SaveableStateHolderTest.kt
index 86f5647..7170fd85 100644
--- a/compose/runtime/runtime-saveable/src/androidAndroidTest/kotlin/androidx/compose/runtime/saveable/SaveableStateHolderTest.kt
+++ b/compose/runtime/runtime-saveable/src/androidAndroidTest/kotlin/androidx/compose/runtime/saveable/SaveableStateHolderTest.kt
@@ -16,12 +16,15 @@
 
 package androidx.compose.runtime.saveable
 
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+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.ui.test.junit4.StateRestorationTester
-import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
@@ -34,7 +37,7 @@
 class SaveableStateHolderTest {
 
     @get:Rule
-    val rule = createComposeRule()
+    val rule = createAndroidComposeRule<Activity>()
 
     private val restorationTester = StateRestorationTester(rule)
 
@@ -256,6 +259,44 @@
             assertThat(restorableNumberOnScreen1).isEqualTo(1)
         }
     }
+
+    @Test
+    fun restoringStateOfThePreviousPageAfterCreatingBundle() {
+        var showFirstPage by mutableStateOf(true)
+        var firstPageState: MutableState<Int>? = null
+
+        rule.setContent {
+            val holder = rememberSaveableStateHolder()
+            holder.SaveableStateProvider(showFirstPage) {
+                if (showFirstPage) {
+                    firstPageState = rememberSaveable { mutableStateOf(0) }
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(firstPageState!!.value).isEqualTo(0)
+            // change the value, so we can assert this change will be restored
+            firstPageState!!.value = 1
+            firstPageState = null
+            showFirstPage = false
+        }
+
+        rule.runOnIdle {
+            rule.activity.doFakeSave()
+            showFirstPage = true
+        }
+
+        rule.runOnIdle {
+            assertThat(firstPageState!!.value).isEqualTo(1)
+        }
+    }
+
+    class Activity : ComponentActivity() {
+        fun doFakeSave() {
+            onSaveInstanceState(Bundle())
+        }
+    }
 }
 
 enum class Screens {
diff --git a/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/ListSaver.kt b/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/ListSaver.kt
index c4fd30e..bcdf4be 100644
--- a/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/ListSaver.kt
+++ b/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/ListSaver.kt
@@ -33,7 +33,10 @@
 ): Saver<Original, Any> = @Suppress("UNCHECKED_CAST") Saver(
     save = {
         val list = save(it)
-        list.forEach { item -> require(canBeSaved(item)) }
+        for (index in list.indices) {
+            val item = list[index]
+            require(canBeSaved(item))
+        }
         if (list.isNotEmpty()) ArrayList(list) else null
     },
     restore = restore as (Any) -> Original?
diff --git a/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateRegistry.kt b/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateRegistry.kt
index 184d2d4..f72e681 100644
--- a/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateRegistry.kt
+++ b/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateRegistry.kt
@@ -144,8 +144,8 @@
                 // the first provider returned null(nothing to save) and the second one returned
                 // "1". when we will be restoring the first provider would restore null (it is the
                 // same as to have nothing to restore) and the second one restore "1".
-                map[key] = list.map {
-                    val value = it.invoke()
+                map[key] = List(list.size) { index ->
+                    val value = list[index].invoke()
                     if (value != null) {
                         check(canBeSaved(value))
                     }
diff --git a/compose/runtime/runtime/api/1.0.0-beta02.txt b/compose/runtime/runtime/api/1.0.0-beta02.txt
index a1c11df..d73a573 100644
--- a/compose/runtime/runtime/api/1.0.0-beta02.txt
+++ b/compose/runtime/runtime/api/1.0.0-beta02.txt
@@ -456,6 +456,9 @@
     method public static boolean isLiveLiteralsEnabled();
   }
 
+  public final class ThreadMapKt {
+  }
+
 }
 
 package androidx.compose.runtime.snapshots {
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index a1c11df..d73a573 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -456,6 +456,9 @@
     method public static boolean isLiveLiteralsEnabled();
   }
 
+  public final class ThreadMapKt {
+  }
+
 }
 
 package androidx.compose.runtime.snapshots {
diff --git a/compose/runtime/runtime/api/public_plus_experimental_1.0.0-beta02.txt b/compose/runtime/runtime/api/public_plus_experimental_1.0.0-beta02.txt
index f900999d..c803652 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_1.0.0-beta02.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_1.0.0-beta02.txt
@@ -544,6 +544,9 @@
     property public abstract int parameters;
   }
 
+  public final class ThreadMapKt {
+  }
+
 }
 
 package androidx.compose.runtime.snapshots {
diff --git a/compose/runtime/runtime/api/public_plus_experimental_current.txt b/compose/runtime/runtime/api/public_plus_experimental_current.txt
index f900999d..c803652 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_current.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_current.txt
@@ -544,6 +544,9 @@
     property public abstract int parameters;
   }
 
+  public final class ThreadMapKt {
+  }
+
 }
 
 package androidx.compose.runtime.snapshots {
diff --git a/compose/runtime/runtime/api/restricted_1.0.0-beta02.txt b/compose/runtime/runtime/api/restricted_1.0.0-beta02.txt
index a334aa4..d80e38d 100644
--- a/compose/runtime/runtime/api/restricted_1.0.0-beta02.txt
+++ b/compose/runtime/runtime/api/restricted_1.0.0-beta02.txt
@@ -483,6 +483,9 @@
     method public static boolean isLiveLiteralsEnabled();
   }
 
+  public final class ThreadMapKt {
+  }
+
 }
 
 package androidx.compose.runtime.snapshots {
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index a334aa4..d80e38d 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -483,6 +483,9 @@
     method public static boolean isLiveLiteralsEnabled();
   }
 
+  public final class ThreadMapKt {
+  }
+
 }
 
 package androidx.compose.runtime.snapshots {
diff --git a/compose/runtime/runtime/build.gradle b/compose/runtime/runtime/build.gradle
index 9d93bb9..1ae86cc 100644
--- a/compose/runtime/runtime/build.gradle
+++ b/compose/runtime/runtime/build.gradle
@@ -16,7 +16,7 @@
 
 import androidx.build.AndroidXUiPlugin
 import androidx.build.LibraryGroups
-import androidx.build.Publish
+import androidx.build.LibraryType
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 import static androidx.build.dependencies.DependenciesKt.*
@@ -118,7 +118,7 @@
 
 androidx {
     name = "Compose Runtime"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.RUNTIME
     inceptionYear = "2019"
     description = "Tree composition support for code generated by the Compose compiler plugin and corresponding public API"
diff --git a/compose/runtime/runtime/compose-runtime-benchmark/lint-baseline.xml b/compose/runtime/runtime/compose-runtime-benchmark/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/runtime/runtime/compose-runtime-benchmark/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/runtime/runtime/integration-tests/lint-baseline.xml b/compose/runtime/runtime/integration-tests/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/runtime/runtime/integration-tests/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/runtime/runtime/lint-baseline.xml b/compose/runtime/runtime/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/runtime/runtime/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/runtime/runtime/samples/lint-baseline.xml b/compose/runtime/runtime/samples/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/runtime/runtime/samples/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/runtime/runtime/src/androidAndroidTest/kotlin/androidx/compose/runtime/snapshots/ParcelableMutableStateTests.kt b/compose/runtime/runtime/src/androidAndroidTest/kotlin/androidx/compose/runtime/snapshots/ParcelableMutableStateTests.kt
new file mode 100644
index 0000000..0cb895a
--- /dev/null
+++ b/compose/runtime/runtime/src/androidAndroidTest/kotlin/androidx/compose/runtime/snapshots/ParcelableMutableStateTests.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.runtime.snapshots
+
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.compose.runtime.SnapshotMutationPolicy
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.neverEqualPolicy
+import androidx.compose.runtime.referentialEqualityPolicy
+import androidx.compose.runtime.structuralEqualityPolicy
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import kotlin.test.assertEquals
+
+@RunWith(Parameterized::class)
+class ParcelableMutableStateTests(
+    private val policy: SnapshotMutationPolicy<Int>
+) {
+    @Test
+    fun saveAndRestoreTheMutableStateOf() {
+        val a = mutableStateOf(0, policy)
+        a.value = 1
+
+        val parcel = Parcel.obtain()
+        parcel.writeParcelable(a as Parcelable, 0)
+        parcel.setDataPosition(0)
+        @Suppress("UNCHECKED_CAST")
+        val restored =
+            parcel.readParcelable<Parcelable>(javaClass.classLoader) as SnapshotMutableState<Int>
+
+        assertEquals(1, restored.value)
+        assertEquals(policy, restored.policy)
+    }
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun initParameters(): Array<SnapshotMutationPolicy<Int>> =
+            arrayOf(
+                structuralEqualityPolicy(),
+                referentialEqualityPolicy(),
+                neverEqualPolicy()
+            )
+    }
+}
diff --git a/compose/runtime/runtime/src/androidMain/kotlin/androidx/compose/runtime/ActualAndroid.android.kt b/compose/runtime/runtime/src/androidMain/kotlin/androidx/compose/runtime/ActualAndroid.android.kt
index 0043c50..7b49ff7 100644
--- a/compose/runtime/runtime/src/androidMain/kotlin/androidx/compose/runtime/ActualAndroid.android.kt
+++ b/compose/runtime/runtime/src/androidMain/kotlin/androidx/compose/runtime/ActualAndroid.android.kt
@@ -18,6 +18,7 @@
 
 import android.os.Looper
 import android.view.Choreographer
+import androidx.compose.runtime.snapshots.SnapshotMutableState
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.runBlocking
@@ -80,3 +81,8 @@
     if (Looper.getMainLooper() != null) DefaultChoreographerFrameClock
     else SdkStubsFallbackFrameClock
 }
+
+internal actual fun <T> createSnapshotMutableState(
+    value: T,
+    policy: SnapshotMutationPolicy<T>
+): SnapshotMutableState<T> = ParcelableSnapshotMutableState(value, policy)
diff --git a/compose/runtime/runtime/src/androidMain/kotlin/androidx/compose/runtime/ParcelableSnapshotMutableState.kt b/compose/runtime/runtime/src/androidMain/kotlin/androidx/compose/runtime/ParcelableSnapshotMutableState.kt
new file mode 100644
index 0000000..05ccc29
--- /dev/null
+++ b/compose/runtime/runtime/src/androidMain/kotlin/androidx/compose/runtime/ParcelableSnapshotMutableState.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.runtime
+
+import android.annotation.SuppressLint
+import android.os.Parcel
+import android.os.Parcelable
+
+@SuppressLint("BanParcelableUsage")
+internal class ParcelableSnapshotMutableState<T>(
+    value: T,
+    policy: SnapshotMutationPolicy<T>
+) : SnapshotMutableStateImpl<T>(value, policy), Parcelable {
+
+    override fun writeToParcel(parcel: Parcel, flags: Int) {
+        parcel.writeValue(value)
+        parcel.writeInt(
+            when (policy) {
+                neverEqualPolicy<Any?>() -> PolicyNeverEquals
+                structuralEqualityPolicy<Any?>() -> PolicyStructuralEquality
+                referentialEqualityPolicy<Any?>() -> PolicyReferentialEquality
+                else -> throw IllegalStateException(
+                    "Only known types of MutableState's SnapshotMutationPolicy are supported"
+                )
+            }
+        )
+    }
+
+    override fun describeContents(): Int {
+        return 0
+    }
+
+    companion object {
+        private const val PolicyNeverEquals = 0
+        private const val PolicyStructuralEquality = 1
+        private const val PolicyReferentialEquality = 2
+
+        @Suppress("unused")
+        @JvmField
+        val CREATOR: Parcelable.Creator<ParcelableSnapshotMutableState<Any?>> =
+            object : Parcelable.ClassLoaderCreator<ParcelableSnapshotMutableState<Any?>> {
+                override fun createFromParcel(
+                    parcel: Parcel,
+                    loader: ClassLoader?
+                ): ParcelableSnapshotMutableState<Any?> {
+                    val value = parcel.readValue(loader ?: javaClass.classLoader)
+                    val policyIndex = parcel.readInt()
+                    return ParcelableSnapshotMutableState(
+                        value,
+                        when (policyIndex) {
+                            PolicyNeverEquals -> neverEqualPolicy()
+                            PolicyStructuralEquality -> structuralEqualityPolicy()
+                            PolicyReferentialEquality -> referentialEqualityPolicy()
+                            else -> throw IllegalStateException(
+                                "Unsupported MutableState policy $policyIndex was restored"
+                            )
+                        }
+                    )
+                }
+
+                override fun createFromParcel(parcel: Parcel) = createFromParcel(parcel, null)
+
+                override fun newArray(size: Int) =
+                    arrayOfNulls<ParcelableSnapshotMutableState<Any?>?>(size)
+            }
+    }
+}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt
index 3b72705..b1bb907 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.runtime
 
+import androidx.compose.runtime.snapshots.fastForEach
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlin.coroutines.Continuation
@@ -103,7 +104,7 @@
         synchronized(lock) {
             if (failureCause != null) return
             failureCause = cause
-            for (awaiter in awaiters) {
+            awaiters.fastForEach { awaiter ->
                 awaiter.continuation.resumeWithException(cause)
             }
             awaiters.clear()
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt
index a54e440..9e9a53b 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt
@@ -187,7 +187,7 @@
 @OptIn(ComposeCompilerApi::class)
 // ComposeNode is a special case of readonly composable and handles creating its own groups, so
 // it is okay to use.
-@Suppress("NONREADONLY_CALL_IN_READONLY_COMPOSABLE")
+@Suppress("NONREADONLY_CALL_IN_READONLY_COMPOSABLE", "UnnecessaryLambdaCreation")
 @Composable inline fun <T : Any, reified E : Applier<*>> ComposeNode(
     noinline factory: () -> T,
     update: @DisallowComposableCalls Updater<T>.() -> Unit
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
index 9a91f829..d970e3c 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
@@ -22,6 +22,7 @@
 
 import androidx.compose.runtime.collection.IdentityScopeMap
 import androidx.compose.runtime.snapshots.fastForEach
+import androidx.compose.runtime.snapshots.fastMap
 import androidx.compose.runtime.snapshots.fastToSet
 import androidx.compose.runtime.tooling.LocalInspectionTables
 import androidx.compose.runtime.tooling.CompositionData
@@ -1252,7 +1253,7 @@
 
     private fun validateRecomposeScopeAnchors(slotTable: SlotTable) {
         val scopes = slotTable.slots.mapNotNull { it as? RecomposeScopeImpl }
-        for (scope in scopes) {
+        scopes.fastForEach { scope ->
             scope.anchor?.let { anchor ->
                 check(scope in slotTable.slotsOf(anchor.toIndexFor(slotTable))) {
                     val dataIndex = slotTable.slots.indexOf(scope)
@@ -1351,7 +1352,7 @@
 
         fun dispatchSideEffects() {
             if (sideEffects.isNotEmpty()) {
-                for (sideEffect in sideEffects) {
+                sideEffects.fastForEach { sideEffect ->
                     sideEffect()
                 }
                 sideEffects.clear()
@@ -1376,7 +1377,7 @@
     internal fun applyChanges() {
         trace("Compose:applyChanges") {
             val invalidationAnchors = slotTable.read { reader ->
-                invalidations.map { reader.anchor(it.location) to it }
+                invalidations.fastMap { reader.anchor(it.location) to it }
             }
 
             val manager = RememberEventDispatcher(abandonSet)
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt
index 2189a52..91193ee 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Expect.kt
@@ -26,6 +26,20 @@
 
 internal fun <T> ThreadLocal() = ThreadLocal<T?> { null }
 
+/**
+ * This is similar to a [ThreadLocal] but has lower overhead because it avoids a weak reference.
+ * This should only be used when the writes are delimited by a try...finally call that will clean
+ * up the reference such as [androidx.compose.runtime.snapshots.Snapshot.enter] else the reference
+ * could get pinned by the thread local causing a leak.
+ *
+ * [ThreadLocal] can be used to implement the actual for platforms that do not exhibit the same
+ * overhead for thread locals as the JVM and ART.
+ */
+internal expect class SnapshotThreadLocal<T>() {
+    fun get(): T?
+    fun set(value: T?)
+}
+
 internal expect fun identityHashCode(instance: Any?): Int
 
 @PublishedApi
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 f20d532..0dfc7c5 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
@@ -21,6 +21,8 @@
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.runtime.snapshots.SnapshotApplyResult
 import androidx.compose.runtime.snapshots.fastForEach
+import androidx.compose.runtime.snapshots.fastMap
+import androidx.compose.runtime.snapshots.fastMapNotNull
 import androidx.compose.runtime.tooling.CompositionData
 import kotlinx.collections.immutable.persistentSetOf
 import kotlinx.coroutines.CancellableContinuation
@@ -298,10 +300,12 @@
         override val changeCount: Long
             get() = [email protected]
         fun saveStateAndDisposeForHotReload(): List<HotReloadable> {
-            val compositions = synchronized(stateLock) { knownCompositions.toList() }
+            val compositions: List<ControlledComposition> = synchronized(stateLock) {
+                knownCompositions.toMutableList()
+            }
             return compositions
-                .mapNotNull { it as? CompositionImpl }
-                .map { HotReloadable(it).apply { clearContent() } }
+                .fastMapNotNull { it as? CompositionImpl }
+                .fastMap { HotReloadable(it).apply { clearContent() } }
         }
     }
 
@@ -868,8 +872,8 @@
             // to ensure that we pause recompositions before this call.
             @Suppress("UNCHECKED_CAST")
             val holders = token as List<HotReloadable>
-            holders.forEach { it.resetContent() }
-            holders.forEach { it.recompose() }
+            holders.fastForEach { it.resetContent() }
+            holders.fastForEach { it.recompose() }
         }
     }
 }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
index 8f7b2c7..57cf35b 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
@@ -17,6 +17,9 @@
 @file:OptIn(InternalComposeApi::class)
 package androidx.compose.runtime
 
+import androidx.compose.runtime.snapshots.fastFilterIndexed
+import androidx.compose.runtime.snapshots.fastForEach
+import androidx.compose.runtime.snapshots.fastMap
 import androidx.compose.runtime.tooling.CompositionData
 import kotlin.math.max
 import kotlin.math.min
@@ -329,7 +332,7 @@
 
         // Verify anchors are well-formed
         var lastLocation = -1
-        for (anchor in anchors) {
+        anchors.fastForEach { anchor ->
             val location = anchor.toIndexFor(this)
             require(location in 0..groupsSize) { "Location out of bound" }
             require(lastLocation < location) { "Anchor is out of order" }
@@ -2210,7 +2213,7 @@
 
         // Insert the anchors into there new location
         val moveDelta = newLocation - originalLocation
-        for (anchor in removedAnchors) {
+        removedAnchors.fastForEach { anchor ->
             val anchorIndex = anchorIndex(anchor)
             val newAnchorIndex = anchorIndex + moveDelta
             if (newAnchorIndex >= groupGapStart) {
@@ -2348,10 +2351,10 @@
     private fun IntArray.dataIndexes() = groups.dataAnchors().let {
         it.slice(0 until groupGapStart) +
             it.slice(groupGapStart + groupGapLen until (size / Group_Fields_Size))
-    }.map { anchor -> dataAnchorToDataIndex(anchor, slotsGapLen, slots.size) }
+    }.fastMap { anchor -> dataAnchorToDataIndex(anchor, slotsGapLen, slots.size) }
 
     @Suppress("unused")
-    private fun keys() = groups.keys().filterIndexed { index, _ ->
+    private fun keys() = groups.keys().fastFilterIndexed { index, _ ->
         index < groupGapStart || index >= groupGapStart + groupGapLen
     }
 
@@ -2543,7 +2546,7 @@
 }
 private fun IntArray.nodeCounts(len: Int = size) =
     slice(GroupInfo_Offset until len step Group_Fields_Size)
-        .map { it and NodeCount_Mask }
+        .fastMap { it and NodeCount_Mask }
 
 // Parent anchor
 private fun IntArray.parentAnchor(address: Int) =
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotState.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotState.kt
index 0622ea6..3e8396f 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotState.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SnapshotState.kt
@@ -63,7 +63,7 @@
 fun <T> mutableStateOf(
     value: T,
     policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy()
-): MutableState<T> = SnapshotMutableStateImpl(value, policy)
+): MutableState<T> = createSnapshotMutableState(value, policy)
 
 /**
  * A value holder where reads to the [value] property during the execution of a [Composable]
@@ -113,17 +113,25 @@
 }
 
 /**
+ * Returns platform specific implementation based on [SnapshotMutableStateImpl].
+ */
+internal expect fun <T> createSnapshotMutableState(
+    value: T,
+    policy: SnapshotMutationPolicy<T>
+): SnapshotMutableState<T>
+
+/**
  * A single value holder whose reads and writes are observed by Compose.
  *
  * Additionally, writes to it are transacted as part of the [Snapshot] system.
  *
- * @property value the wrapped value
- * @property policy a policy to control how changes are handled in a mutable snapshot.
+ * @param value the wrapped value
+ * @param policy a policy to control how changes are handled in a mutable snapshot.
  *
  * @see mutableStateOf
  * @see SnapshotMutationPolicy
  */
-private class SnapshotMutableStateImpl<T>(
+internal open class SnapshotMutableStateImpl<T>(
     value: T,
     override val policy: SnapshotMutationPolicy<T>
 ) : StateObject, SnapshotMutableState<T> {
@@ -243,6 +251,8 @@
 
 private object ReferentialEqualityPolicy : SnapshotMutationPolicy<Any?> {
     override fun equivalent(a: Any?, b: Any?) = a === b
+
+    override fun toString() = "ReferentialEqualityPolicy"
 }
 
 /**
@@ -258,6 +268,8 @@
 
 private object StructuralEqualityPolicy : SnapshotMutationPolicy<Any?> {
     override fun equivalent(a: Any?, b: Any?) = a == b
+
+    override fun toString() = "StructuralEqualityPolicy"
 }
 
 /**
@@ -273,6 +285,8 @@
 
 private object NeverEqualPolicy : SnapshotMutationPolicy<Any?> {
     override fun equivalent(a: Any?, b: Any?) = false
+
+    override fun toString() = "NeverEqualPolicy"
 }
 
 /**
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/ListUtils.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/ListUtils.kt
index ddd647c..2e6554c 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/ListUtils.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/ListUtils.kt
@@ -16,12 +16,146 @@
 
 package androidx.compose.runtime.snapshots
 
-internal inline fun <T> List<T>.fastForEach(block: (element: T) -> Unit) {
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+
+/**
+ * Iterates through a [List] using the index and calls [action] for each item.
+ * This does not allocate an iterator like [Iterable.forEach].
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T> List<T>.fastForEach(action: (T) -> Unit) {
+    contract { callsInPlace(action) }
     for (index in indices) {
-        block(this[index])
+        val item = get(index)
+        action(item)
     }
 }
 
+/**
+ * Returns a [Set] of all elements.
+ *
+ * The returned set preserves the element iteration order of the original collection.
+ */
 internal fun <T> List<T>.fastToSet(): Set<T> = HashSet<T>(size).also { set ->
     fastForEach { item -> set.add(item) }
-}
\ No newline at end of file
+}
+
+/**
+ * Iterates through a [List] using the index and calls [action] for each item.
+ * This does not allocate an iterator like [Iterable.forEachIndexed].
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T> List<T>.fastForEachIndexed(action: (Int, T) -> Unit) {
+    contract { callsInPlace(action) }
+    for (index in indices) {
+        val item = get(index)
+        action(index, item)
+    }
+}
+
+/**
+ * Returns a list containing the results of applying the given [transform] function
+ * to each element in the original collection.
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T, R> List<T>.fastMap(transform: (T) -> R): List<R> {
+    contract { callsInPlace(transform) }
+    val target = ArrayList<R>(size)
+    fastForEach {
+        target += transform(it)
+    }
+    return target
+}
+
+/**
+ * Creates a string from all the elements separated using [separator] and using the given [prefix]
+ * and [postfix] if supplied.
+ *
+ * If the collection could be huge, you can specify a non-negative value of [limit], in which case
+ * only the first [limit] elements will be appended, followed by the [truncated] string (which
+ * defaults to "...").
+ */
+internal fun <T> List<T>.fastJoinToString(
+    separator: CharSequence = ", ",
+    prefix: CharSequence = "",
+    postfix: CharSequence = "",
+    limit: Int = -1,
+    truncated: CharSequence = "...",
+    transform: ((T) -> CharSequence)? = null
+): String {
+    return fastJoinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform)
+        .toString()
+}
+
+/**
+ * Appends the string from all the elements separated using [separator] and using the given
+ * [prefix] and [postfix] if supplied.
+ *
+ * If the collection could be huge, you can specify a non-negative value of [limit], in which
+ * case only the first [limit] elements will be appended, followed by the [truncated] string
+ * (which defaults to "...").
+ */
+private fun <T, A : Appendable> List<T>.fastJoinTo(
+    buffer: A,
+    separator: CharSequence = ", ",
+    prefix: CharSequence = "",
+    postfix: CharSequence = "",
+    limit: Int = -1,
+    truncated: CharSequence = "...",
+    transform: ((T) -> CharSequence)? = null
+): A {
+    buffer.append(prefix)
+    var count = 0
+    for (index in indices) {
+        val element = get(index)
+        if (++count > 1) buffer.append(separator)
+        if (limit < 0 || count <= limit) {
+            buffer.appendElement(element, transform)
+        } else break
+    }
+    if (limit >= 0 && count > limit) buffer.append(truncated)
+    buffer.append(postfix)
+    return buffer
+}
+
+/**
+ * Copied from Appendable.kt
+ */
+private fun <T> Appendable.appendElement(element: T, transform: ((T) -> CharSequence)?) {
+    when {
+        transform != null -> append(transform(element))
+        element is CharSequence? -> append(element)
+        element is Char -> append(element)
+        else -> append(element.toString())
+    }
+}
+
+/**
+ * Returns a list containing the results of applying the given [transform] function
+ * to each element in the original collection.
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T, R> List<T>.fastMapNotNull(transform: (T) -> R?): List<R> {
+    contract { callsInPlace(transform) }
+    val target = ArrayList<R>(size)
+    fastForEach { e ->
+        transform(e)?.let { target += it }
+    }
+    return target
+}
+
+/**
+ * Returns a list containing only elements matching the given [predicate].
+ * @param [predicate] function that takes the index of an element and the element itself
+ * and returns the result of predicate evaluation on the element.
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T> List<T>.fastFilterIndexed(predicate: (index: Int, T) -> Boolean): List<T> {
+    contract { callsInPlace(predicate) }
+    val target = ArrayList<T>(size)
+    fastForEachIndexed { index, e ->
+        if (predicate(index, e)) target += e
+    }
+    return target
+}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
index 2f73c00..6414755 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
@@ -20,7 +20,7 @@
 
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.InternalComposeApi
-import androidx.compose.runtime.ThreadLocal
+import androidx.compose.runtime.SnapshotThreadLocal
 import androidx.compose.runtime.synchronized
 
 /**
@@ -588,7 +588,7 @@
                 takeNewGlobalSnapshot(previousGlobalSnapshot, emptyLambda)
                 val globalModified = previousGlobalSnapshot.modified
                 if (globalModified != null && globalModified.isNotEmpty())
-                    applyObservers.toList() to globalModified
+                    applyObservers.toMutableList() to globalModified
                 else
                     emptyList<(Set<Any>, Snapshot) -> Unit>() to null
             } else {
@@ -609,7 +609,7 @@
                 this.modified = null
                 previousGlobalSnapshot.modified = null
 
-                applyObservers.toList() to globalModified
+                applyObservers.toMutableList() to globalModified
             }
         }
 
@@ -1129,7 +1129,7 @@
                 } else null
                 )?.let {
                 it.firstOrNull() ?: { state: Any ->
-                    it.forEach { it(state) }
+                    it.fastForEach { it(state) }
                 }
             }
         }
@@ -1355,7 +1355,10 @@
  */
 private const val INVALID_SNAPSHOT = 0
 
-private val threadSnapshot = ThreadLocal<Snapshot>()
+/**
+ * Current thread snapshot
+ */
+private val threadSnapshot = SnapshotThreadLocal<Snapshot>()
 
 // A global synchronization object. This synchronization object should be taken before modifying any
 // of the fields below.
@@ -1426,7 +1429,7 @@
     // observers.
     val modified = previousGlobalSnapshot.modified
     if (modified != null) {
-        val observers = sync { applyObservers.toList() }
+        val observers: List<(Set<Any>, Snapshot) -> Unit> = sync { applyObservers.toMutableList() }
         observers.fastForEach { observer ->
             observer(modified, previousGlobalSnapshot)
         }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotIdSet.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotIdSet.kt
index 46c56c2..0416373 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotIdSet.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotIdSet.kt
@@ -293,7 +293,7 @@
 
     override fun toString(): String = "${super.toString()} [${this.map {
         it.toString()
-    }.joinToString()}]"
+    }.fastJoinToString()}]"
 
     companion object {
         /**
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateMap.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateMap.kt
index 02e60d0..d8daf9c 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateMap.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateMap.kt
@@ -160,7 +160,7 @@
         return removed
     }
     override fun retainAll(elements: Collection<MutableMap.MutableEntry<K, V>>): Boolean {
-        val entries = elements.map { it.key to it.value }.toMap()
+        val entries = elements.associate { it.key to it.value }
         return map.removeIf { !entries.containsKey(it.key) || entries[it.key] != it.value }
     }
     override fun contains(element: MutableMap.MutableEntry<K, V>): Boolean {
diff --git a/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualDesktop.desktop.kt b/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualDesktop.desktop.kt
index 06f7e35..b704879 100644
--- a/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualDesktop.desktop.kt
+++ b/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualDesktop.desktop.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.runtime
 
+import androidx.compose.runtime.snapshots.SnapshotMutableState
 import kotlinx.coroutines.delay
 
 internal actual object Trace {
@@ -71,3 +72,8 @@
         return onFrame(System.nanoTime())
     }
 }
+
+internal actual fun <T> createSnapshotMutableState(
+    value: T,
+    policy: SnapshotMutationPolicy<T>
+): SnapshotMutableState<T> = SnapshotMutableStateImpl(value, policy)
diff --git a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/ActualJvm.jvm.kt b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/ActualJvm.jvm.kt
index 7aa0d96..63dbcb1 100644
--- a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/ActualJvm.jvm.kt
+++ b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/ActualJvm.jvm.kt
@@ -16,6 +16,9 @@
 
 package androidx.compose.runtime
 
+import androidx.compose.runtime.internal.ThreadMap
+import androidx.compose.runtime.internal.emptyThreadMap
+
 internal actual typealias AtomicReference<V> = java.util.concurrent.atomic.AtomicReference<V>
 
 internal actual open class ThreadLocal<T> actual constructor(
@@ -35,6 +38,23 @@
     }
 }
 
+internal actual class SnapshotThreadLocal<T> {
+    private val map = AtomicReference<ThreadMap>(emptyThreadMap)
+    private val writeMutex = Any()
+
+    @Suppress("UNCHECKED_CAST")
+    actual fun get(): T? = map.get().get(Thread.currentThread().id) as T?
+
+    actual fun set(value: T?) {
+        val key = Thread.currentThread().id
+        synchronized(writeMutex) {
+            val current = map.get()
+            if (current.trySet(key, value)) return
+            map.set(current.newWith(key, value))
+        }
+    }
+}
+
 internal actual fun identityHashCode(instance: Any?): Int = System.identityHashCode(instance)
 
 @PublishedApi
diff --git a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ThreadMap.kt b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ThreadMap.kt
new file mode 100644
index 0000000..d4645eb
--- /dev/null
+++ b/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/internal/ThreadMap.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.runtime.internal
+
+internal class ThreadMap(
+    private val size: Int,
+    private val keys: LongArray,
+    private val values: Array<Any?>
+) {
+    fun get(key: Long): Any? {
+        val index = find(key)
+        return if (index >= 0) values[index] else null
+    }
+
+    /**
+     * Set the value if it is already in the map. Otherwise a new map must be allocated to contain
+     * the new entry.
+     */
+    fun trySet(key: Long, value: Any?): Boolean {
+        val index = find(key)
+        if (index < 0) return false
+        values[index] = value
+        return true
+    }
+
+    fun newWith(key: Long, value: Any?): ThreadMap {
+        val size = size
+        val newSize = values.count { it != null } + 1
+        val newKeys = LongArray(newSize)
+        val newValues = arrayOfNulls<Any?>(newSize)
+        if (newSize > 1) {
+            var dest = 0
+            var source = 0
+            while (dest < newSize && source < size) {
+                val oldKey = keys[source]
+                val oldValue = values[source]
+                if (oldKey > key) {
+                    newKeys[dest] = key
+                    newValues[dest] = value
+                    dest++
+                    // Continue with a loop without this check
+                    break
+                }
+                if (oldValue != null) {
+                    newKeys[dest] = oldKey
+                    newValues[dest] = oldValue
+                    dest++
+                }
+                source++
+            }
+            if (source == size) {
+                // Appending a value to the end.
+                newKeys[newSize - 1] = key
+                newValues[newSize - 1] = value
+            } else {
+                while (dest < newSize) {
+                    val oldKey = keys[source]
+                    val oldValue = values[source]
+                    if (oldValue != null) {
+                        newKeys[dest] = oldKey
+                        newValues[dest] = oldValue
+                        dest++
+                    }
+                    source++
+                }
+            }
+        } else {
+            // The only element
+            newKeys[0] = key
+            newValues[0] = value
+        }
+        return ThreadMap(newSize, newKeys, newValues)
+    }
+
+    private fun find(key: Long): Int {
+        var high = size - 1
+        when (high) {
+            -1 -> return -1
+            0 -> return if (keys[0] == key) 0 else if (keys[0] > key) -2 else -1
+        }
+        var low = 0
+
+        while (low <= high) {
+            val mid = (low + high).ushr(1)
+            val midVal = keys[mid]
+            val comparison = midVal - key
+            when {
+                comparison < 0 -> low = mid + 1
+                comparison > 0 -> high = mid - 1
+                else -> return mid
+            }
+        }
+        return -(low + 1)
+    }
+}
+
+internal val emptyThreadMap = ThreadMap(0, LongArray(0), emptyArray())
\ No newline at end of file
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotThreadMapTests.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotThreadMapTests.kt
new file mode 100644
index 0000000..a50c9a8
--- /dev/null
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotThreadMapTests.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.runtime.snapshots
+
+import androidx.compose.runtime.SnapshotThreadLocal
+import androidx.compose.runtime.internal.ThreadMap
+import kotlin.random.Random
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+
+/**
+ * Test the internal ThreadMap
+ */
+class SnapshotThreadMapTests {
+    @Test
+    fun canCreateAMap() {
+        val map = emptyThreadMap()
+        assertNotNull(map)
+    }
+
+    @Test
+    fun setOfEmptyFails() {
+        val map = emptyThreadMap()
+        val added = map.trySet(1, 1)
+        assertFalse(added)
+    }
+
+    @Test
+    fun canAddOneToEmpty() {
+        val map = emptyThreadMap()
+        val newMap = map.newWith(1, 1)
+        assertNotEquals(map, newMap)
+        assertEquals(1, newMap.get(1))
+    }
+
+    @Test
+    fun canCreateForward() {
+        val map = testMap(0 until 100)
+        assertNotNull(map)
+        for (i in 0 until 100) {
+            assertEquals(i, map.get(i.toLong()))
+        }
+        for (i in -100 until 0) {
+            assertNull(map.get(i.toLong()))
+        }
+        for (i in 100 until 200) {
+            assertNull(map.get(i.toLong()))
+        }
+    }
+
+    @Test
+    fun canCreateBackward() {
+        val map = testMap((0 until 100).reversed())
+        assertNotNull(map)
+        for (i in 0 until 100) {
+            assertEquals(i, map.get(i.toLong()))
+        }
+        for (i in -100 until 0) {
+            assertNull(map.get(i.toLong()))
+        }
+        for (i in 100 until 200) {
+            assertNull(map.get(i.toLong()))
+        }
+    }
+
+    @Test
+    fun canCreateRandom() {
+        val list = Array<Long>(100) { it.toLong() }
+        val rand = Random(1337)
+        list.shuffle(rand)
+        var map = emptyThreadMap()
+        for (item in list) {
+            map = map.newWith(item, item)
+        }
+        for (i in 0 until 100) {
+            assertEquals(i.toLong(), map.get(i.toLong()))
+        }
+        for (i in -100 until 0) {
+            assertNull(map.get(i.toLong()))
+        }
+        for (i in 100 until 200) {
+            assertNull(map.get(i.toLong()))
+        }
+    }
+
+    @Test
+    fun canRemoveOne() {
+        val map = testMap(1..10)
+        val set = map.trySet(5, null)
+        assertTrue(set)
+        for (i in 1..10) {
+            if (i == 5) {
+                assertNull(map.get(i.toLong()))
+            } else {
+                assertEquals(i, map.get(i.toLong()))
+            }
+        }
+    }
+
+    @Test
+    fun canRemoveOneThenAddOne() {
+        val map = testMap(1..10)
+        val set = map.trySet(5, null)
+        assertTrue(set)
+        val newMap = map.newWith(11, 11)
+        assertNull(newMap.get(5))
+        assertEquals(11, newMap.get(11))
+    }
+
+    private fun emptyThreadMap() = ThreadMap(0, LongArray(0), arrayOfNulls(0))
+
+    private fun testMap(intProgression: IntProgression): ThreadMap {
+        var result = emptyThreadMap()
+        for (i in intProgression) {
+            result = result.newWith(i.toLong(), i)
+        }
+        return result
+    }
+}
+
+/**
+ * Test the thread lcoal variable
+ */
+class SnapshotThreadLocalTests {
+    @Test
+    fun canCreate() {
+        val local = SnapshotThreadLocal<Int>()
+        assertNotNull(local)
+    }
+
+    @Test
+    fun initalValueIsNull() {
+        val local = SnapshotThreadLocal<Int>()
+        assertNull(local.get())
+    }
+
+    @Test
+    fun canSetAndGetTheValue() {
+        val local = SnapshotThreadLocal<Int>()
+        local.set(100)
+        assertEquals(100, local.get())
+    }
+}
\ No newline at end of file
diff --git a/compose/test-utils/lint-baseline.xml b/compose/test-utils/lint-baseline.xml
index 988d5bd..0eddc06 100644
--- a/compose/test-utils/lint-baseline.xml
+++ b/compose/test-utils/lint-baseline.xml
@@ -1,26 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
+<issues format="5" by="lint 4.2.0-beta04" client="gradle" variant="debug" version="4.2.0-beta04">
 
     <issue
         id="BanSynchronizedMethods"
@@ -40,29 +19,7 @@
         errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.android.kt"
-            line="349"
-            column="1"/>
-    </issue>
-
-    <issue
-        id="BanTargetApiAnnotation"
-        message="Uses @TargetApi annotation"
-        errorLine1="@TargetApi(Build.VERSION_CODES.Q)"
-        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/testutils/benchmark/android/AndroidTestCaseRunner.android.kt"
-            line="159"
-            column="1"/>
-    </issue>
-
-    <issue
-        id="BanTargetApiAnnotation"
-        message="Uses @TargetApi annotation"
-        errorLine1="@TargetApi(Build.VERSION_CODES.Q)"
-        errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/testutils/benchmark/BenchmarkHelpers.android.kt"
-            line="42"
+            line="344"
             column="1"/>
     </issue>
 
@@ -73,7 +30,7 @@
         errorLine2="                            ~~~~~~~~~~~~">
         <location
             file="src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.android.kt"
-            line="278"
+            line="273"
             column="29"/>
     </issue>
 
@@ -84,7 +41,7 @@
         errorLine2="                             ~~~~~~~~~~~~~~~~~~">
         <location
             file="src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.android.kt"
-            line="351"
+            line="346"
             column="30"/>
     </issue>
 
@@ -95,118 +52,30 @@
         errorLine2="                   ~~~~~~~~~~~">
         <location
             file="src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.android.kt"
+            line="349"
+            column="20"/>
+    </issue>
+
+    <issue
+        id="UnsafeNewApiCall"
+        message="This call is to a method from API 29, the call containing class androidx.compose.testutils.RenderNodeCapture is not annotated with @RequiresApi(x) where x is at least 29. Either annotate the containing class with at least @RequiresApi(29) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(29)."
+        errorLine1="        return renderNode.beginRecording()"
+        errorLine2="                          ~~~~~~~~~~~~~~">
+        <location
+            file="src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.android.kt"
+            line="350"
+            column="27"/>
+    </issue>
+
+    <issue
+        id="UnsafeNewApiCall"
+        message="This call is to a method from API 29, the call containing class androidx.compose.testutils.RenderNodeCapture is not annotated with @RequiresApi(x) where x is at least 29. Either annotate the containing class with at least @RequiresApi(29) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(29)."
+        errorLine1="        renderNode.endRecording()"
+        errorLine2="                   ~~~~~~~~~~~~">
+        <location
+            file="src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.android.kt"
             line="354"
             column="20"/>
     </issue>
 
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 29, the call containing class androidx.compose.testutils.RenderNodeCapture is not annotated with @RequiresApi(x) where x is at least 29. Either annotate the containing class with at least @RequiresApi(29) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(29)."
-        errorLine1="        return renderNode.beginRecording()"
-        errorLine2="                          ~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.android.kt"
-            line="355"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 29, the call containing class androidx.compose.testutils.RenderNodeCapture is not annotated with @RequiresApi(x) where x is at least 29. Either annotate the containing class with at least @RequiresApi(29) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(29)."
-        errorLine1="        renderNode.endRecording()"
-        errorLine2="                   ~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.android.kt"
-            line="359"
-            column="20"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 29, the call containing class androidx.compose.testutils.benchmark.android.RenderNodeCapture is not annotated with @RequiresApi(x) where x is at least 29. Either annotate the containing class with at least @RequiresApi(29) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(29)."
-        errorLine1="    private val renderNode = RenderNode(&quot;Test&quot;)"
-        errorLine2="                             ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/testutils/benchmark/android/AndroidTestCaseRunner.android.kt"
-            line="161"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 29, the call containing class androidx.compose.testutils.benchmark.android.RenderNodeCapture is not annotated with @RequiresApi(x) where x is at least 29. Either annotate the containing class with at least @RequiresApi(29) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(29)."
-        errorLine1="        renderNode.setPosition(0, 0, width, height)"
-        errorLine2="                   ~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/testutils/benchmark/android/AndroidTestCaseRunner.android.kt"
-            line="164"
-            column="20"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 29, the call containing class androidx.compose.testutils.benchmark.android.RenderNodeCapture is not annotated with @RequiresApi(x) where x is at least 29. Either annotate the containing class with at least @RequiresApi(29) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(29)."
-        errorLine1="        return renderNode.beginRecording()"
-        errorLine2="                          ~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/testutils/benchmark/android/AndroidTestCaseRunner.android.kt"
-            line="165"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 29, the call containing class androidx.compose.testutils.benchmark.android.RenderNodeCapture is not annotated with @RequiresApi(x) where x is at least 29. Either annotate the containing class with at least @RequiresApi(29) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(29)."
-        errorLine1="        renderNode.endRecording()"
-        errorLine2="                   ~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/testutils/benchmark/android/AndroidTestCaseRunner.android.kt"
-            line="169"
-            column="20"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 29, the call containing class androidx.compose.testutils.benchmark.RenderNodeCapture is not annotated with @RequiresApi(x) where x is at least 29. Either annotate the containing class with at least @RequiresApi(29) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(29)."
-        errorLine1="    private val renderNode = RenderNode(&quot;Test&quot;)"
-        errorLine2="                             ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/testutils/benchmark/BenchmarkHelpers.android.kt"
-            line="44"
-            column="30"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 29, the call containing class androidx.compose.testutils.benchmark.RenderNodeCapture is not annotated with @RequiresApi(x) where x is at least 29. Either annotate the containing class with at least @RequiresApi(29) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(29)."
-        errorLine1="        renderNode.setPosition(0, 0, width, height)"
-        errorLine2="                   ~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/testutils/benchmark/BenchmarkHelpers.android.kt"
-            line="47"
-            column="20"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 29, the call containing class androidx.compose.testutils.benchmark.RenderNodeCapture is not annotated with @RequiresApi(x) where x is at least 29. Either annotate the containing class with at least @RequiresApi(29) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(29)."
-        errorLine1="        return Canvas(renderNode.beginRecording())"
-        errorLine2="                                 ~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/testutils/benchmark/BenchmarkHelpers.android.kt"
-            line="48"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 29, the call containing class androidx.compose.testutils.benchmark.RenderNodeCapture is not annotated with @RequiresApi(x) where x is at least 29. Either annotate the containing class with at least @RequiresApi(29) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(29)."
-        errorLine1="        renderNode.endRecording()"
-        errorLine2="                   ~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/testutils/benchmark/BenchmarkHelpers.android.kt"
-            line="52"
-            column="20"/>
-    </issue>
-
 </issues>
diff --git a/compose/ui/ui-android-stubs/build.gradle b/compose/ui/ui-android-stubs/build.gradle
index fee733e..f0fc4ca 100644
--- a/compose/ui/ui-android-stubs/build.gradle
+++ b/compose/ui/ui-android-stubs/build.gradle
@@ -15,10 +15,7 @@
  */
 
 import androidx.build.LibraryGroups
-import androidx.build.LibraryVersions
-import androidx.build.Publish
-
-import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryType
 
 plugins {
     id("AndroidXPlugin")
@@ -38,7 +35,7 @@
 
 androidx {
     name = "Compose Android Stubs"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.UI
     inceptionYear = "2020"
     description = "Stubs for classes in older Android APIs"
diff --git a/compose/ui/ui-android-stubs/lint-baseline.xml b/compose/ui/ui-android-stubs/lint-baseline.xml
deleted file mode 100644
index 8f1aa4b..0000000
--- a/compose/ui/ui-android-stubs/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-</issues>
diff --git a/compose/ui/ui-geometry/build.gradle b/compose/ui/ui-geometry/build.gradle
index 94acbf8..ee955c2 100644
--- a/compose/ui/ui-geometry/build.gradle
+++ b/compose/ui/ui-geometry/build.gradle
@@ -17,7 +17,7 @@
 
 import androidx.build.AndroidXUiPlugin
 import androidx.build.LibraryGroups
-import androidx.build.Publish
+import androidx.build.LibraryType
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 import static androidx.build.dependencies.DependenciesKt.*
@@ -81,7 +81,7 @@
 
 androidx {
     name = "Compose Geometry"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.UI
     inceptionYear = "2020"
     description = "Compose classes related to dimensions without units"
diff --git a/compose/ui/ui-geometry/lint-baseline.xml b/compose/ui/ui-geometry/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/ui/ui-geometry/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/ui/ui-graphics/build.gradle b/compose/ui/ui-graphics/build.gradle
index fe48e41..8a2b0b5 100644
--- a/compose/ui/ui-graphics/build.gradle
+++ b/compose/ui/ui-graphics/build.gradle
@@ -16,8 +16,7 @@
 
 import androidx.build.AndroidXUiPlugin
 import androidx.build.LibraryGroups
-import androidx.build.LibraryVersions
-import androidx.build.Publish
+import androidx.build.LibraryType
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 import static androidx.build.dependencies.DependenciesKt.*
@@ -120,7 +119,7 @@
 
 androidx {
     name = "Compose Graphics"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.UI
     inceptionYear = "2020"
     description = "Compose graphics"
diff --git a/compose/ui/ui-graphics/lint-baseline.xml b/compose/ui/ui-graphics/lint-baseline.xml
index 6ad5a4a..f5995f5 100644
--- a/compose/ui/ui-graphics/lint-baseline.xml
+++ b/compose/ui/ui-graphics/lint-baseline.xml
@@ -1,37 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 29, the call containing class androidx.compose.ui.graphics.AndroidPaint_androidKt is not annotated with @RequiresApi(x) where x is at least 29. Either annotate the containing class with at least @RequiresApi(29) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(29)."
-        errorLine1="        this.blendMode = mode.toAndroidBlendMode()"
-        errorLine2="             ~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidPaint.android.kt"
-            line="134"
-            column="14"/>
-    </issue>
+<issues format="5" by="lint 4.2.0-beta04" client="gradle" variant="debug" version="4.2.0-beta04">
 
     <issue
         id="UnsafeNewApiCall"
diff --git a/compose/ui/ui-graphics/samples/lint-baseline.xml b/compose/ui/ui-graphics/samples/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/ui/ui-graphics/samples/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color.kt
index 6bee7c6..677ecb4 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color.kt
@@ -452,26 +452,26 @@
  */
 @Stable
 fun lerp(start: Color, stop: Color, /*@FloatRange(from = 0.0, to = 1.0)*/ fraction: Float): Color {
-    val linearColorSpace = ColorSpaces.LinearExtendedSrgb
-    val startColor = start.convert(linearColorSpace)
-    val endColor = stop.convert(linearColorSpace)
+    val colorSpace = ColorSpaces.Oklab
+    val startColor = start.convert(colorSpace)
+    val endColor = stop.convert(colorSpace)
 
-    val startA = startColor.alpha
-    val startR = startColor.red
-    val startG = startColor.green
+    val startAlpha = startColor.alpha
+    val startL = startColor.red
+    val startA = startColor.green
     val startB = startColor.blue
 
-    val endA = endColor.alpha
-    val endR = endColor.red
-    val endG = endColor.green
+    val endAlpha = endColor.alpha
+    val endL = endColor.red
+    val endA = endColor.green
     val endB = endColor.blue
 
     val interpolated = Color(
-        alpha = lerp(startA, endA, fraction),
-        red = lerp(startR, endR, fraction),
-        green = lerp(startG, endG, fraction),
+        alpha = lerp(startAlpha, endAlpha, fraction),
+        red = lerp(startL, endL, fraction),
+        green = lerp(startA, endA, fraction),
         blue = lerp(startB, endB, fraction),
-        colorSpace = linearColorSpace
+        colorSpace = colorSpace
     )
     return interpolated.convert(stop.colorSpace)
 }
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Path.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Path.kt
index cbdbe4d..3604e94 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Path.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Path.kt
@@ -199,10 +199,6 @@
     /**
      * Adds a new subpath that consists of the given `path` offset by the given
      * `offset`.
-     *
-     * If `matrix4` is specified, the path will be transformed by this matrix
-     * after the matrix is translated by the given offset. The matrix is a 4x4
-     * matrix stored in column major order.
      */
     fun addPath(path: Path, offset: Offset = Offset.Zero)
 
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Vertices.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Vertices.kt
index f6c305d..d8f495cc 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Vertices.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Vertices.kt
@@ -17,6 +17,7 @@
 package androidx.compose.ui.graphics
 
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.util.fastAny
 
 /**  A set of vertex data used by [Canvas.drawVertices]. */
 class Vertices(
@@ -38,7 +39,7 @@
             throw IllegalArgumentException("positions and textureCoordinates lengths must match.")
         if (colors.size != positions.size)
             throw IllegalArgumentException("positions and colors lengths must match.")
-        if (indices.any(outOfBounds))
+        if (indices.fastAny(outOfBounds))
             throw IllegalArgumentException(
                 "indices values must be valid indices " +
                     "in the positions list."
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/ColorSpaces.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/ColorSpaces.kt
index a657b97..78d8f95 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/ColorSpaces.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/ColorSpaces.kt
@@ -266,6 +266,23 @@
     )
 
     /**
+     * [Lab][ColorModel.Lab] color space Oklab. This color space uses Oklab D65
+     * as a profile conversion space.
+     *
+     * ```
+     * | Property                | Value                                                   |
+     * |-------------------------|---------------------------------------------------------|
+     * | Name                    | Oklab                                                   |
+     * | CIE standard illuminant | [D65][Illuminant.D65]                                   |
+     * | Range                   | (L: `[0.0, 1.0]`, a: `[-2, 2]`, b: `[-2, 2]`)           |
+     * ```
+     */
+    internal val Oklab: ColorSpace = Oklab(
+        "Oklab",
+        id = 17
+    )
+
+    /**
      * Returns a [ColorSpaces] instance of [ColorSpace] that matches
      * the specified RGB to CIE XYZ transform and transfer functions. If no
      * instance can be found, this method returns null.
@@ -322,6 +339,7 @@
         Acescg,
         CieXyz,
         CieLab,
-        Unspecified
+        Unspecified,
+        Oklab
     )
 }
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/Oklab.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/Oklab.kt
new file mode 100644
index 0000000..6cda41d
--- /dev/null
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/colorspace/Oklab.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.ui.graphics.colorspace
+
+import kotlin.math.pow
+
+/**
+ * Implementation of the Oklab color space. Oklab uses
+ * a D65 white point.
+ */
+internal class Oklab(
+    name: String,
+    id: Int
+) : ColorSpace(
+    name,
+    ColorModel.Lab, id
+) {
+
+    override val isWideGamut: Boolean
+        get() = true
+
+    override fun getMinValue(component: Int): Float {
+        return if (component == 0) 0f else -2f
+    }
+
+    override fun getMaxValue(component: Int): Float {
+        return if (component == 0) 1f else 2f
+    }
+
+    override fun toXyz(v: FloatArray): FloatArray {
+        v[0] = v[0].coerceIn(0f, 1f)
+        v[1] = v[1].coerceIn(-2f, 2f)
+        v[2] = v[2].coerceIn(-2f, 2f)
+
+        mul3x3Float3(InverseM2, v)
+        v[0] = v[0].pow(3f)
+        v[1] = v[1].pow(3f)
+        v[2] = v[2].pow(3f)
+        mul3x3Float3(InverseM1, v)
+
+        return v
+    }
+
+    override fun fromXyz(v: FloatArray): FloatArray {
+        mul3x3Float3(M1, v)
+
+        v[0] = v[0].pow(1f / 3f)
+        v[1] = v[1].pow(1f / 3f)
+        v[2] = v[2].pow(1f / 3f)
+
+        mul3x3Float3(M2, v)
+        return v
+    }
+
+    internal companion object {
+        /**
+         * This is the matrix applied before the nonlinear transform for (D50) XYZ-to-Oklab.
+         * This combines the D50-to-D65 white point transform with the normal transform matrix
+         * because this is always done together in [fromXyz].
+         */
+        private val M1 = mul3x3(
+            floatArrayOf(
+                0.8189330101f, 0.0329845436f, 0.0482003018f,
+                0.3618667424f, 0.9293118715f, 0.2643662691f,
+                -0.1288597137f, 0.0361456387f, 0.6338517070f
+            ),
+            chromaticAdaptation(
+                matrix = Adaptation.VonKries.transform,
+                srcWhitePoint = Illuminant.D50.toXyz(),
+                dstWhitePoint = Illuminant.D65.toXyz()
+            )
+        )
+
+        /**
+         * Matrix applied after the nonlinear transform.
+         */
+        private val M2 = floatArrayOf(
+            0.2104542553f, 1.9779984951f, 0.0259040371f,
+            0.7936177850f, -2.4285922050f, 0.7827717662f,
+            -0.0040720468f, 0.4505937099f, -0.8086757660f
+        )
+
+        /**
+         * The inverse of the [M1] matrix, transforming back to XYZ (D50)
+         */
+        private val InverseM1 = inverse3x3(M1)
+
+        /**
+         * The inverse of the [M2] matrix, doing the first linear transform in the
+         * Oklab-to-XYZ before doing the nonlinear transform.
+         */
+        private val InverseM2 = inverse3x3(M2)
+    }
+}
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathParser.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathParser.kt
index 996cd0d..2836f51 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathParser.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathParser.kt
@@ -36,6 +36,7 @@
 import androidx.compose.ui.graphics.vector.PathNode.RelativeReflectiveQuadTo
 import androidx.compose.ui.graphics.vector.PathNode.RelativeVerticalTo
 import androidx.compose.ui.graphics.vector.PathNode.VerticalTo
+import androidx.compose.ui.util.fastForEach
 import kotlin.math.PI
 import kotlin.math.abs
 import kotlin.math.atan2
@@ -108,7 +109,7 @@
         reflectiveCtrlPoint.reset()
 
         var previousNode: PathNode? = null
-        for (node in nodes) {
+        nodes.fastForEach { node ->
             if (previousNode == null) previousNode = node
             when (node) {
                 is Close -> close(target)
@@ -123,13 +124,13 @@
                 is RelativeCurveTo -> node.relativeCurveTo(target)
                 is CurveTo -> node.curveTo(target)
                 is RelativeReflectiveCurveTo ->
-                    node.relativeReflectiveCurveTo(previousNode.isCurve, target)
-                is ReflectiveCurveTo -> node.reflectiveCurveTo(previousNode.isCurve, target)
+                    node.relativeReflectiveCurveTo(previousNode!!.isCurve, target)
+                is ReflectiveCurveTo -> node.reflectiveCurveTo(previousNode!!.isCurve, target)
                 is RelativeQuadTo -> node.relativeQuadTo(target)
                 is QuadTo -> node.quadTo(target)
                 is RelativeReflectiveQuadTo ->
-                    node.relativeReflectiveQuadTo(previousNode.isQuad, target)
-                is ReflectiveQuadTo -> node.reflectiveQuadTo(previousNode.isQuad, target)
+                    node.relativeReflectiveQuadTo(previousNode!!.isQuad, target)
+                is ReflectiveQuadTo -> node.reflectiveQuadTo(previousNode!!.isQuad, target)
                 is RelativeArcTo -> node.relativeArcTo(target)
                 is ArcTo -> node.arcTo(target)
             }
diff --git a/compose/ui/ui-graphics/src/test/java/androidx/compose/ui/graphics/ColorTest.kt b/compose/ui/ui-graphics/src/test/java/androidx/compose/ui/graphics/ColorTest.kt
index da18ac6..eaa0c4f 100644
--- a/compose/ui/ui-graphics/src/test/java/androidx/compose/ui/graphics/ColorTest.kt
+++ b/compose/ui/ui-graphics/src/test/java/androidx/compose/ui/graphics/ColorTest.kt
@@ -106,27 +106,27 @@
         val red = Color.Red
         val green = Color.Green
 
-        val redLinear = red.convert(ColorSpaces.LinearExtendedSrgb)
-        val greenLinear = green.convert(ColorSpaces.LinearExtendedSrgb)
+        val redOklab = red.convert(ColorSpaces.Oklab)
+        val greenOklab = green.convert(ColorSpaces.Oklab)
 
         for (i in 0..255) {
             val t = i / 255f
             val color = lerp(red, green, t)
-            val expectedLinear = Color(
-                red = lerp(redLinear.red, greenLinear.red, t),
+            val expectedOklab = Color(
+                red = lerp(redOklab.red, greenOklab.red, t),
                 green = lerp(
-                    redLinear.green,
-                    greenLinear.green,
+                    redOklab.green,
+                    greenOklab.green,
                     t
                 ),
                 blue = lerp(
-                    redLinear.blue,
-                    greenLinear.blue,
+                    redOklab.blue,
+                    greenOklab.blue,
                     t
                 ),
-                colorSpace = ColorSpaces.LinearExtendedSrgb
+                colorSpace = ColorSpaces.Oklab
             )
-            val expected = expectedLinear.convert(ColorSpaces.Srgb)
+            val expected = expectedOklab.convert(ColorSpaces.Srgb)
             val colorARGB = Color(color.toArgb())
             val expectedARGB = Color(expected.toArgb())
             assertEquals(
diff --git a/compose/ui/ui-inspection/lint-baseline.xml b/compose/ui/ui-inspection/lint-baseline.xml
deleted file mode 100644
index 8f1aa4b..0000000
--- a/compose/ui/ui-inspection/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-</issues>
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/ParametersTest.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/ParametersTest.kt
index 3a3b4ce..d5f8f58e 100644
--- a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/ParametersTest.kt
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/ParametersTest.kt
@@ -103,12 +103,14 @@
 
         val intArray = params.find("intArray")!!
         var strings = params.stringsList
+
+        checkStringParam(strings, intArray, "intArray", "IntArray[8]", 0)
         assertThat(intArray.elementsCount).isEqualTo(5)
-        checkParam(strings, intArray.elementsList[0], "[0]", 10)
-        checkParam(strings, intArray.elementsList[1], "[1]", 11)
-        checkParam(strings, intArray.elementsList[2], "[2]", 12)
-        checkParam(strings, intArray.elementsList[3], "[3]", 13)
-        checkParam(strings, intArray.elementsList[4], "[4]", 14)
+        checkIntParam(strings, intArray.elementsList[0], "[0]", 10, 0)
+        checkIntParam(strings, intArray.elementsList[1], "[1]", 11, 1)
+        checkIntParam(strings, intArray.elementsList[2], "[2]", 12, 2)
+        checkIntParam(strings, intArray.elementsList[3], "[3]", 13, 3)
+        checkIntParam(strings, intArray.elementsList[4], "[4]", 14, 4)
 
         val expanded =
             tester.sendCommand(
@@ -121,10 +123,11 @@
             ).getParameterDetailsResponse
         val intArray2 = expanded.parameter
         strings = expanded.stringsList
+        checkStringParam(strings, intArray, "intArray", "IntArray[8]", 0)
         assertThat(intArray2.elementsCount).isEqualTo(3)
-        checkParam(strings, intArray2.elementsList[0], "[5]", 15)
-        checkParam(strings, intArray2.elementsList[1], "[6]", 16)
-        checkParam(strings, intArray2.elementsList[2], "[7]", 17)
+        checkIntParam(strings, intArray2.elementsList[0], "[5]", 15, 5)
+        checkIntParam(strings, intArray2.elementsList[1], "[6]", 16, 6)
+        checkIntParam(strings, intArray2.elementsList[2], "[7]", 17, 7)
     }
 }
 
@@ -149,12 +152,25 @@
 private fun ComposableNode.flatten(): List<ComposableNode> =
     listOf(this).plus(this.childrenList.flatMap { it.flatten() })
 
-private fun checkParam(
+@Suppress("SameParameterValue")
+private fun checkStringParam(
+    stringList: List<StringEntry>,
+    param: Parameter,
+    name: String,
+    value: String,
+    index: Int = 0
+) {
+    assertThat(stringList.toMap()[param.name]).isEqualTo(name)
+    assertThat(stringList.toMap()[param.int32Value]).isEqualTo(value)
+    assertThat(param.index).isEqualTo(index)
+}
+
+private fun checkIntParam(
     stringList: List<StringEntry>,
     param: Parameter,
     name: String,
     value: Int,
-    index: Int = -1
+    index: Int = 0
 ) {
     assertThat(stringList.toMap()[param.name]).isEqualTo(name)
     assertThat(param.int32Value).isEqualTo(value)
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/UnknownResponseTest.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/UnknownResponseTest.kt
new file mode 100644
index 0000000..d4df199
--- /dev/null
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/UnknownResponseTest.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.ui.inspection
+
+import androidx.compose.ui.inspection.rules.ComposeInspectionRule
+import androidx.compose.ui.inspection.testdata.TestActivity
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.Command
+import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.Response
+import org.junit.Rule
+import org.junit.Test
+
+@LargeTest
+class UnknownResponseTest {
+    @get:Rule
+    val rule = ComposeInspectionRule(TestActivity::class)
+
+    @Test
+    fun invalidBytesReturnedAsUnknownResponse(): Unit = runBlocking {
+        val invalidBytes = (1..99).map { it.toByte() }.toByteArray()
+        val responseBytes = rule.inspectorTester.sendCommand(invalidBytes)
+        val response = Response.parseFrom(responseBytes)
+
+        assertThat(response.specializedCase)
+            .isEqualTo(Response.SpecializedCase.UNKNOWN_COMMAND_RESPONSE)
+        assertThat(response.unknownCommandResponse.commandBytes.toByteArray())
+            .isEqualTo(invalidBytes)
+    }
+
+    @Test
+    fun unhandledCommandCaseReturnedAsUnknownResponse(): Unit = runBlocking {
+        val invalidCommand = Command.getDefaultInstance()
+        // This invalid case is handled by an else branch in ComposeLayoutInspector. In practice,
+        // this could also happen when a newer version of Studio sends a new command to an older
+        // version of an inspector.
+        assertThat(invalidCommand.specializedCase)
+            .isEqualTo(Command.SpecializedCase.SPECIALIZED_NOT_SET)
+
+        val commandBytes = invalidCommand.toByteArray()
+        val responseBytes = rule.inspectorTester.sendCommand(commandBytes)
+        val response = Response.parseFrom(responseBytes)
+
+        assertThat(response.specializedCase)
+            .isEqualTo(Response.SpecializedCase.UNKNOWN_COMMAND_RESPONSE)
+        assertThat(response.unknownCommandResponse.commandBytes.toByteArray())
+            .isEqualTo(commandBytes)
+    }
+}
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt
index c98b9a9..e4636e2 100644
--- a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt
@@ -72,6 +72,8 @@
 
 private const val DEBUG = false
 private const val ROOT_ID = 3L
+private const val MAX_RECURSIONS = 2
+private const val MAX_ITERABLE_SIZE = 5
 
 @LargeTest
 @RunWith(AndroidJUnit4::class)
@@ -457,7 +459,8 @@
             }
 
             if (checkParameters) {
-                val params = builder.convertParameters(ROOT_ID, node)
+                val params =
+                    builder.convertParameters(ROOT_ID, node, MAX_RECURSIONS, MAX_ITERABLE_SIZE)
                 val receiver = ParameterValidationReceiver(params.listIterator())
                 receiver.block()
                 receiver.checkFinished(name)
@@ -523,7 +526,10 @@
         println()
         print(")")
         if (generateParameters && node.parameters.isNotEmpty()) {
-            generateParameters(builder.convertParameters(ROOT_ID, node), 0)
+            generateParameters(
+                builder.convertParameters(ROOT_ID, node, MAX_RECURSIONS, MAX_ITERABLE_SIZE),
+                0
+            )
         }
         println()
         node.children.forEach { generateValidate(it, builder) }
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt
index 4fc2af8..6954ac7 100644
--- a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt
@@ -83,6 +83,8 @@
 private const val ROOT_ID = 3L
 private const val NODE_ID = -7L
 private const val PARAM_INDEX = 4
+private const val MAX_RECURSIONS = 2
+private const val MAX_ITERABLE_SIZE = 5
 
 @Suppress("unused")
 private fun topLevelFunction() {
@@ -92,8 +94,6 @@
 @RunWith(AndroidJUnit4::class)
 class ParameterFactoryTest {
     private val factory = ParameterFactory(InlineClassConverter())
-    private val originalMaxRecursions = factory.maxRecursions
-    private val originalMaxIterable = factory.maxIterable
     private val node = MutableInspectorNode().apply {
         width = 1000
         height = 500
@@ -108,8 +108,6 @@
 
     @After
     fun after() {
-        factory.maxRecursions = originalMaxRecursions
-        factory.maxIterable = originalMaxIterable
         isDebugInspectorInfoEnabled = false
     }
 
@@ -218,13 +216,10 @@
             )
         ) {
             parameter("brush", ParameterType.String, "LinearGradient") {
-                parameter("colors", ParameterType.Iterable, "") {
+                parameter("colors", ParameterType.Iterable, "List[2]") {
                     parameter("[0]", ParameterType.Color, Color.Red.toArgb())
                     parameter("[1]", ParameterType.Color, Color.Blue.toArgb())
                 }
-                // Parameters are traversed in alphabetical order through reflection queries.
-                // Validate createdSize exists before validating end parameter
-                parameter("createdSize", ParameterType.String, "Unspecified", index = 5)
                 parameter("end", ParameterType.String, Offset::class.java.simpleName) {
                     parameter("x", ParameterType.DimensionDp, 2.5f)
                     parameter("y", ParameterType.DimensionDp, 5.0f)
@@ -234,6 +229,7 @@
                     parameter("y", ParameterType.DimensionDp, 0.25f)
                 }
                 parameter("tileMode", ParameterType.String, "Clamp", index = 4)
+                parameter("createdSize", ParameterType.String, "Unspecified", index = 5)
             }
         }
         // TODO: add tests for RadialGradient & ShaderBrush
@@ -291,13 +287,12 @@
         }
     }
 
-    @Ignore
     @Test
     fun testCornerSize() {
         assertThat(lookup(ZeroCornerSize)).isEqualTo(ParameterType.String to "ZeroCornerSize")
         assertThat(lookup(CornerSize(2.4.dp))).isEqualTo(ParameterType.DimensionDp to 2.4f)
-        assertThat(lookup(CornerSize(2.4f))).isEqualTo(ParameterType.DimensionDp to 1.2f)
-        assertThat(lookup(CornerSize(3))).isEqualTo(ParameterType.DimensionDp to 7.5f)
+        assertThat(lookup(CornerSize(2.4f))).isEqualTo(ParameterType.String to "2.4px")
+        assertThat(lookup(CornerSize(3))).isEqualTo(ParameterType.String to "3.0%")
     }
 
     @Test
@@ -423,7 +418,7 @@
     @Test
     fun testLocaleList() {
         validate(create("locales", LocaleList(Locale("fr-ca"), Locale("fr-be")))) {
-            parameter("locales", ParameterType.Iterable, "") {
+            parameter("locales", ParameterType.Iterable, "Collection[2]") {
                 parameter("[0]", ParameterType.String, "fr-CA")
                 parameter("[1]", ParameterType.String, "fr-BE")
             }
@@ -437,11 +432,10 @@
 
     @Test
     fun testShortIntArray() {
-        factory.maxIterable = 10
         val value = intArrayOf(10, 11, 12)
         val parameter = create("array", value)
         validate(parameter) {
-            parameter("array", ParameterType.Iterable, "") {
+            parameter("array", ParameterType.Iterable, "IntArray[3]") {
                 parameter("[0]", ParameterType.Int32, 10)
                 parameter("[1]", ParameterType.Int32, 11)
                 parameter("[2]", ParameterType.Int32, 12)
@@ -451,12 +445,12 @@
 
     @Test
     fun testLongIntArray() {
-        factory.maxIterable = 5
         val value = intArrayOf(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23)
         val refToSelf = ref()
+        val display = "IntArray[14]"
         val parameter = create("array", value)
         validate(parameter) {
-            parameter("array", ParameterType.Iterable, "", refToSelf) {
+            parameter("array", ParameterType.Iterable, display, refToSelf) {
                 parameter("[0]", ParameterType.Int32, 10)
                 parameter("[1]", ParameterType.Int32, 11)
                 parameter("[2]", ParameterType.Int32, 12)
@@ -465,9 +459,9 @@
             }
         }
 
-        // If we need to retrieve more array elements we call "factory.expand" with the reference:
-        validate(factory.expand(ROOT_ID, node, "array", value, refToSelf, 5, 5)!!) {
-            parameter("array", ParameterType.Iterable, "", refToSelf) {
+        // If we need to retrieve more array elements we call "expand" with the reference:
+        validate(expand("array", value, refToSelf, 5, 5)!!) {
+            parameter("array", ParameterType.Iterable, display, refToSelf, childStartIndex = 5) {
                 parameter("[5]", ParameterType.Int32, 15)
                 parameter("[6]", ParameterType.Int32, 16)
                 parameter("[7]", ParameterType.Int32, 17)
@@ -476,10 +470,10 @@
             }
         }
 
-        // Call "factory.expand" again to retrieve more:
-        validate(factory.expand(ROOT_ID, node, "array", value, refToSelf, 10, 5)!!) {
+        // Call "expand" again to retrieve more:
+        validate(expand("array", value, refToSelf, 10, 5)!!) {
             // This time we reached the end of the array, and we do not get a reference to get more
-            parameter("array", ParameterType.Iterable, "") {
+            parameter("array", ParameterType.Iterable, display, childStartIndex = 10) {
                 parameter("[10]", ParameterType.Int32, 20)
                 parameter("[11]", ParameterType.Int32, 21)
                 parameter("[12]", ParameterType.Int32, 22)
@@ -490,23 +484,56 @@
 
     @Test
     fun testListWithNullElement() {
-        factory.maxIterable = 3
-        val value = listOf("Hello", null, "World")
+        val value = listOf(
+            "a",
+            null,
+            "b",
+            "c",
+            null,
+            null,
+            null,
+            null,
+            "d",
+            null,
+            "e",
+            null,
+            null,
+            null,
+            null,
+            null,
+            "f",
+            null,
+            "g",
+            null
+        )
         val parameter = create("array", value)
+        val refToSelf = ref()
+        val display = "List[20]"
         validate(parameter) {
             // Here we get all the available elements from the list.
             // There is no need to go back for more data, and the iterable does not have a
             // reference for doing so.
-            parameter("array", ParameterType.Iterable, "") {
-                parameter("[0]", ParameterType.String, "Hello")
-                parameter("[2]", ParameterType.String, "World", index = 2)
+            parameter("array", ParameterType.Iterable, display, refToSelf) {
+                parameter("[0]", ParameterType.String, "a")
+                parameter("[2]", ParameterType.String, "b", index = 2)
+                parameter("[3]", ParameterType.String, "c", index = 3)
+                parameter("[8]", ParameterType.String, "d", index = 8)
+                parameter("[10]", ParameterType.String, "e", index = 10)
+            }
+        }
+
+        // Call "expand" to retrieve more elements:
+        validate(expand("array", value, refToSelf, 11, 5)!!) {
+            // This time we reached the end of the array, and we do not get a reference to get more
+            parameter("array", ParameterType.Iterable, display) {
+                parameter("[16]", ParameterType.String, "f", index = 16)
+                parameter("[18]", ParameterType.String, "g", index = 18)
             }
         }
     }
 
     @Test
     fun testModifier() {
-        factory.maxRecursions = 4
         validate(
             create(
                 "modifier",
@@ -517,7 +544,8 @@
                     .fillMaxWidth()
                     .wrapContentHeight(Alignment.Bottom)
                     .width(30.0.dp)
-                    .paint(TestPainter(10f, 20f))
+                    .paint(TestPainter(10f, 20f)),
+                maxRecursions = 4
             )
         ) {
             parameter("modifier", ParameterType.String, "") {
@@ -526,9 +554,9 @@
                     parameter("shape", ParameterType.String, "RectangleShape")
                 }
                 parameter("border", ParameterType.Color, Color.Red.toArgb()) {
+                    parameter("width", ParameterType.DimensionDp, 5.0f)
                     parameter("color", ParameterType.Color, Color.Red.toArgb())
                     parameter("shape", ParameterType.String, "RectangleShape")
-                    parameter("width", ParameterType.DimensionDp, 5.0f)
                 }
                 parameter("padding", ParameterType.DimensionDp, 2.0f)
                 parameter("fillMaxWidth", ParameterType.String, "") {
@@ -540,13 +568,8 @@
                 }
                 parameter("width", ParameterType.DimensionDp, 30.0f)
                 parameter("paint", ParameterType.String, "") {
-                    parameter("alignment", ParameterType.String, "Center")
-                    parameter("alpha", ParameterType.Float, 1.0f)
-                    parameter("contentScale", ParameterType.String, "Inside")
                     parameter("painter", ParameterType.String, "TestPainter") {
-                        parameter("alpha", ParameterType.Float, 1.0f)
                         parameter("color", ParameterType.Color, Color.Red.toArgb())
-                        parameter("drawLambda", ParameterType.Lambda, null, index = 6)
                         parameter("height", ParameterType.Float, 20.0f)
                         parameter("intrinsicSize", ParameterType.String, "Size") {
                             parameter("height", ParameterType.Float, 20.0f)
@@ -555,11 +578,16 @@
                             parameter("packedValue", ParameterType.Int64, 4692750812821061632L)
                             parameter("width", ParameterType.Float, 10.0f)
                         }
+                        parameter("width", ParameterType.Float, 10.0f)
+                        parameter("alpha", ParameterType.Float, 1.0f)
+                        parameter("drawLambda", ParameterType.Lambda, null, index = 6)
                         parameter("layoutDirection", ParameterType.String, "Ltr", index = 8)
                         parameter("useLayer", ParameterType.Boolean, false, index = 9)
-                        parameter("width", ParameterType.Float, 10.0f)
                     }
                     parameter("sizeToIntrinsics", ParameterType.Boolean, true)
+                    parameter("alignment", ParameterType.String, "Center")
+                    parameter("contentScale", ParameterType.String, "Inside")
+                    parameter("alpha", ParameterType.Float, 1.0f)
                 }
             }
         }
@@ -579,10 +607,10 @@
         validate(create("modifier", Modifier.padding(1.dp, 2.dp, 3.dp, 4.dp))) {
             parameter("modifier", ParameterType.String, "") {
                 parameter("padding", ParameterType.String, "") {
-                    parameter("bottom", ParameterType.DimensionDp, 4.0f)
-                    parameter("end", ParameterType.DimensionDp, 3.0f)
                     parameter("start", ParameterType.DimensionDp, 1.0f)
                     parameter("top", ParameterType.DimensionDp, 2.0f)
+                    parameter("end", ParameterType.DimensionDp, 3.0f)
+                    parameter("bottom", ParameterType.DimensionDp, 4.0f)
                 }
             }
         }
@@ -643,9 +671,7 @@
         val name = MyClass::class.java.simpleName
 
         // Limit the recursions for this test to validate parameter nodes with missing children.
-        factory.maxRecursions = 2
-
-        val parameter = create("v1", v1)
+        val parameter = create("v1", v1, maxRecursions = 2)
         val v2ref = ref(3, 1)
         validate(parameter) {
             parameter("v1", ParameterType.String, name) {
@@ -663,9 +689,9 @@
         }
 
         // If we need to retrieve the missing child nodes for v2 from above, we must
-        // call "factory.expand" with the reference:
+        // call "expand" with the reference:
         val v4ref = ref(3, 1, 1, 1)
-        validate(factory.expand(ROOT_ID, node, "v1", v1, v2ref)!!) {
+        validate(expand("v1", v1, v2ref)!!) {
             parameter("other", ParameterType.String, name) {
                 parameter("name", ParameterType.String, "v3")
                 parameter("other", ParameterType.String, name) {
@@ -679,8 +705,8 @@
         }
 
         // If we need to retrieve the missing child nodes for v4 from above, we must
-        // call "factory.expand" with the reference:
-        validate(factory.expand(ROOT_ID, node, "v1", v1, v4ref)!!) {
+        // call "expand" with the reference:
+        validate(expand("v1", v1, v4ref)!!) {
             parameter("other", ParameterType.String, name) {
                 parameter("name", ParameterType.String, "v5")
             }
@@ -802,16 +828,57 @@
         assertThat(lookup(Icons.Rounded.Add)).isEqualTo(ParameterType.String to "Rounded.Add")
     }
 
-    private fun create(name: String, value: Any): NodeParameter {
-        val parameter = factory.create(ROOT_ID, node, name, value, PARAM_INDEX)
+    private fun create(
+        name: String,
+        value: Any,
+        maxRecursions: Int = MAX_RECURSIONS,
+        maxInitialIterableSize: Int = MAX_ITERABLE_SIZE
+    ): NodeParameter {
+        val parameter = factory.create(
+            ROOT_ID,
+            node,
+            name,
+            value,
+            PARAM_INDEX,
+            maxRecursions,
+            maxInitialIterableSize
+        )
 
         // Check that factory.expand will return the exact same information as factory.create
         // for each parameter and parameter child. Punt if there are references.
-        checkExpand(parameter, parameter.name, value, mutableListOf())
+        checkExpand(
+            parameter,
+            parameter.name,
+            value,
+            mutableListOf(),
+            maxRecursions,
+            maxInitialIterableSize
+        )
 
         return parameter
     }
 
+    private fun expand(
+        name: String,
+        value: Any?,
+        reference: NodeParameterReference,
+        startIndex: Int = 0,
+        maxElements: Int = MAX_ITERABLE_SIZE,
+        maxRecursions: Int = MAX_RECURSIONS,
+        maxInitialIterableSize: Int = MAX_ITERABLE_SIZE
+    ): NodeParameter? =
+        factory.expand(
+            ROOT_ID,
+            node,
+            name,
+            value,
+            reference,
+            startIndex,
+            maxElements,
+            maxRecursions,
+            maxInitialIterableSize
+        )
+
     private fun lookup(value: Any): Pair<ParameterType, Any?> {
         val parameter = create("parameter", value)
         assertThat(parameter.elements).isEmpty()
@@ -834,11 +901,19 @@
         parameter: NodeParameter,
         name: String,
         value: Any,
-        indices: MutableList<Int>
+        indices: MutableList<Int>,
+        maxRecursions: Int,
+        maxInitialIterableSize: Int
     ) {
         factory.clearCacheFor(ROOT_ID)
         val reference = NodeParameterReference(NODE_ID, PARAM_INDEX, indices)
-        val expanded = factory.expand(ROOT_ID, node, name, value, reference)
+        val expanded = expand(
+            name,
+            value,
+            reference,
+            maxRecursions = maxRecursions,
+            maxInitialIterableSize = maxInitialIterableSize
+        )
         if (parameter.value == null && indices.isNotEmpty()) {
             assertThat(expanded).isNull()
         } else {
@@ -847,7 +922,14 @@
                 parameter.elements.forEach { element ->
                     if (element.index >= 0) {
                         indices.add(element.index)
-                        checkExpand(element, name, value, indices)
+                        checkExpand(
+                            element,
+                            name,
+                            value,
+                            indices,
+                            maxRecursions,
+                            maxInitialIterableSize
+                        )
                         indices.removeLast()
                     }
                 }
@@ -877,8 +959,9 @@
 }
 
 class ParameterValidationReceiver(
-    private val parameterIterator: Iterator<NodeParameter>,
-    private val trace: String = ""
+    private val parameterIterator: ListIterator<NodeParameter>,
+    private val trace: String = "",
+    private val startIndex: Int = 0
 ) {
     fun parameter(
         name: String,
@@ -886,26 +969,26 @@
         value: Any?,
         ref: NodeParameterReference? = null,
         index: Int = -1,
+        childStartIndex: Int = 0,
         block: ParameterValidationReceiver.() -> Unit = {}
     ) {
+        val listIndex = startIndex + parameterIterator.nextIndex()
+        val expectedIndex = if (index < 0) listIndex else index
         assertWithMessage("No such element found: $name").that(parameterIterator.hasNext()).isTrue()
         val parameter = parameterIterator.next()
         assertThat(parameter.name).isEqualTo(name)
         val msg = "$trace${parameter.name}"
         assertWithMessage(msg).that(parameter.type).isEqualTo(type)
-        assertWithMessage(msg).that(parameter.index).isEqualTo(index)
+        assertWithMessage(msg).that(parameter.index).isEqualTo(expectedIndex)
         assertWithMessage(msg).that(checkEquals(parameter.reference, ref)).isTrue()
         if (type != ParameterType.Lambda || value != null) {
             assertWithMessage(msg).that(parameter.value).isEqualTo(value)
         }
-        var elements: List<NodeParameter> = parameter.elements
-        if (name != "modifier" && type != ParameterType.Iterable) {
-            // Do not sort modifiers or iterables: the order is important
-            elements = elements.sortedBy { it.name }
+        val iterator = parameter.elements.listIterator()
+        ParameterValidationReceiver(iterator, "$msg.", childStartIndex).apply {
+            block()
+            checkFinished(msg)
         }
-        val children = ParameterValidationReceiver(elements.listIterator(), "$msg.")
-        children.block()
-        children.checkFinished(msg)
     }
 
     fun checkFinished(trace: String = "") {
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
index 3da3c70..a454265 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/ComposeLayoutInspector.kt
@@ -33,6 +33,8 @@
 import androidx.inspection.Inspector
 import androidx.inspection.InspectorEnvironment
 import androidx.inspection.InspectorFactory
+import com.google.protobuf.ByteString
+import com.google.protobuf.InvalidProtocolBufferException
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.Command
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.GetAllParametersCommand
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.GetAllParametersResponse
@@ -44,8 +46,11 @@
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.GetParametersResponse
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.ParameterGroup
 import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.Response
+import layoutinspector.compose.inspection.LayoutInspectorComposeProtocol.UnknownCommandResponse
 
 private const val LAYOUT_INSPECTION_ID = "layoutinspector.compose.inspection"
+private const val MAX_RECURSIONS = 2
+private const val MAX_ITERABLE_SIZE = 5
 
 // created by java.util.ServiceLoader
 class ComposeLayoutInspectorFactory :
@@ -83,7 +88,13 @@
         }
 
     override fun onReceiveCommand(data: ByteArray, callback: CommandCallback) {
-        val command = Command.parseFrom(data)
+        val command = try {
+            Command.parseFrom(data)
+        } catch (ignored: InvalidProtocolBufferException) {
+            handleUnknownCommand(data, callback)
+            return
+        }
+
         when (command.specializedCase) {
             Command.SpecializedCase.GET_COMPOSABLES_COMMAND -> {
                 handleGetComposablesCommand(command.getComposablesCommand, callback)
@@ -97,7 +108,15 @@
             Command.SpecializedCase.GET_PARAMETER_DETAILS_COMMAND -> {
                 handleGetParameterDetailsCommand(command.getParameterDetailsCommand, callback)
             }
-            else -> error("Unexpected compose inspector command case: ${command.specializedCase}")
+            else -> handleUnknownCommand(data, callback)
+        }
+    }
+
+    private fun handleUnknownCommand(commandBytes: ByteArray, callback: CommandCallback) {
+        callback.reply {
+            unknownCommandResponse = UnknownCommandResponse.newBuilder().apply {
+                this.commandBytes = ByteString.copyFrom(commandBytes)
+            }.build()
         }
     }
 
@@ -144,13 +163,15 @@
                 getParametersCommand.skipSystemComposables
             )[getParametersCommand.composableId]
 
-        val rootId = getParametersCommand.rootViewId
-
         callback.reply {
             getParametersResponse = if (foundComposable != null) {
                 val stringTable = StringTable()
-                val parameters = foundComposable.convertParameters(layoutInspectorTree, rootId)
-                    .convertAll(stringTable)
+                val parameters = foundComposable.convertParameters(
+                    layoutInspectorTree,
+                    getParametersCommand.rootViewId,
+                    getParametersCommand.maxRecursions.orElse(MAX_RECURSIONS),
+                    getParametersCommand.maxInitialIterableSize.orElse(MAX_ITERABLE_SIZE),
+                ).convertAll(stringTable)
                 GetParametersResponse.newBuilder().apply {
                     parameterGroup = ParameterGroup.newBuilder().apply {
                         composableId = getParametersCommand.composableId
@@ -174,13 +195,15 @@
                 getAllParametersCommand.skipSystemComposables
             ).values
 
-        val rootId = getAllParametersCommand.rootViewId
-
         callback.reply {
             val stringTable = StringTable()
             val parameterGroups = allComposables.map { composable ->
-                val parameters = composable.convertParameters(layoutInspectorTree, rootId)
-                    .convertAll(stringTable)
+                val parameters = composable.convertParameters(
+                    layoutInspectorTree,
+                    getAllParametersCommand.rootViewId,
+                    getAllParametersCommand.maxRecursions.orElse(MAX_RECURSIONS),
+                    getAllParametersCommand.maxInitialIterableSize.orElse(MAX_ITERABLE_SIZE),
+                ).convertAll(stringTable)
                 ParameterGroup.newBuilder().apply {
                     composableId = composable.id
                     addAllParameter(parameters)
@@ -188,7 +211,7 @@
             }
 
             getAllParametersResponse = GetAllParametersResponse.newBuilder().apply {
-                rootViewId = rootId
+                rootViewId = getAllParametersCommand.rootViewId
                 addAllParameterGroups(parameterGroups)
                 addAllStrings(stringTable.toStringEntries())
             }.build()
@@ -214,7 +237,9 @@
                 composable,
                 reference,
                 getParameterDetailsCommand.startIndex,
-                getParameterDetailsCommand.maxElements
+                getParameterDetailsCommand.maxElements,
+                getParameterDetailsCommand.maxRecursions.orElse(MAX_RECURSIONS),
+                getParameterDetailsCommand.maxInitialIterableSize.orElse(MAX_ITERABLE_SIZE),
             )
         }
 
@@ -300,3 +325,7 @@
         .flatMap { it.flatten() }
         .toList()
 }
+
+// Provide default for older version:
+private fun Int.orElse(defaultValue: Int): Int =
+    if (this == 0) defaultValue else this
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/ComposeExtensions.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/ComposeExtensions.kt
index f98479d..455f084 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/ComposeExtensions.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/ComposeExtensions.kt
@@ -29,10 +29,17 @@
  */
 fun InspectorNode.convertParameters(
     layoutInspectorTree: LayoutInspectorTree,
-    rootId: Long
+    rootId: Long,
+    maxRecursions: Int,
+    maxInitialIterableSize: Int
 ): List<NodeParameter> {
     ThreadUtils.assertOffMainThread()
-    return layoutInspectorTree.convertParameters(rootId, this)
+    return layoutInspectorTree.convertParameters(
+        rootId,
+        this,
+        maxRecursions,
+        maxInitialIterableSize
+    )
 }
 
 /**
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
index 43b8e2d..305feee 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTree.kt
@@ -110,9 +110,22 @@
     /**
      * Converts the [RawParameter]s of the [node] into displayable parameters.
      */
-    fun convertParameters(rootId: Long, node: InspectorNode): List<NodeParameter> {
+    fun convertParameters(
+        rootId: Long,
+        node: InspectorNode,
+        maxRecursions: Int,
+        maxInitialIterableSize: Int
+    ): List<NodeParameter> {
         return node.parameters.mapIndexed { index, parameter ->
-            parameterFactory.create(rootId, node, parameter.name, parameter.value, index)
+            parameterFactory.create(
+                rootId,
+                node,
+                parameter.name,
+                parameter.value,
+                index,
+                maxRecursions,
+                maxInitialIterableSize
+            )
         }
     }
 
@@ -126,7 +139,9 @@
         node: InspectorNode,
         reference: NodeParameterReference,
         startIndex: Int,
-        maxElements: Int
+        maxElements: Int,
+        maxRecursions: Int,
+        maxInitialIterableSize: Int
     ): NodeParameter? {
         if (reference.parameterIndex !in node.parameters.indices) {
             return null
@@ -139,7 +154,9 @@
             parameter.value,
             reference,
             startIndex,
-            maxElements
+            maxElements,
+            maxRecursions,
+            maxInitialIterableSize
         )
     }
 
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/NodeParameter.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/NodeParameter.kt
index f9057e6..5cee350 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/NodeParameter.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/NodeParameter.kt
@@ -47,10 +47,8 @@
 
     /**
      * The index into the composite parent parameter value.
-     *
-     * If the index is identical to index of the parent element list then this value will be -1.
      */
-    var index = -1
+    var index = 0
 }
 
 /**
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/ParameterFactory.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/ParameterFactory.kt
index 4d3d165..483feab 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/ParameterFactory.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/inspector/ParameterFactory.kt
@@ -43,7 +43,6 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.TextUnit
 import androidx.compose.ui.unit.TextUnitType
-import org.jetbrains.annotations.TestOnly
 import java.lang.reflect.Field
 import java.util.IdentityHashMap
 import kotlin.jvm.internal.FunctionReference
@@ -59,9 +58,6 @@
 import kotlin.reflect.jvm.javaGetter
 import java.lang.reflect.Modifier as JavaModifier
 
-private const val MAX_RECURSIONS = 2
-private const val MAX_ITERABLE_SIZE = 5
-
 private val reflectionScope: ReflectionScope = ReflectionScope()
 
 /**
@@ -100,12 +96,6 @@
 
     var density = Density(1.0f)
 
-    @set:TestOnly
-    var maxRecursions = MAX_RECURSIONS
-
-    @set:TestOnly
-    var maxIterable = MAX_ITERABLE_SIZE
-
     init {
         val textDecorationCombination = TextDecoration.combine(
             listOf(TextDecoration.LineThrough, TextDecoration.Underline)
@@ -134,12 +124,22 @@
         node: InspectorNode,
         name: String,
         value: Any?,
-        parameterIndex: Int
+        parameterIndex: Int,
+        maxRecursions: Int,
+        maxInitialIterableSize: Int
     ): NodeParameter {
         val creator = creatorCache ?: ParameterCreator()
         try {
             return reflectionScope.withReflectiveAccess {
-                creator.create(rootId, node, name, value, parameterIndex)
+                creator.create(
+                    rootId,
+                    node,
+                    name,
+                    value,
+                    parameterIndex,
+                    maxRecursions,
+                    maxInitialIterableSize
+                )
             }
         } finally {
             creatorCache = creator
@@ -154,6 +154,8 @@
      * @param value is the value of the [reference].parameterIndex'th parameter of [node].
      * @param startIndex is the index of the 1st wanted element of a List/Array.
      * @param maxElements is the max number of elements wanted from a List/Array.
+     * @param maxRecursions is the max recursion into composite types starting from reference.
+     * @param maxInitialIterableSize is the max number of elements wanted in new List/Array values.
      */
     fun expand(
         rootId: Long,
@@ -161,13 +163,25 @@
         name: String,
         value: Any?,
         reference: NodeParameterReference,
-        startIndex: Int = 0,
-        maxElements: Int = maxIterable
+        startIndex: Int,
+        maxElements: Int,
+        maxRecursions: Int,
+        maxInitialIterableSize: Int
     ): NodeParameter? {
         val creator = creatorCache ?: ParameterCreator()
         try {
             return reflectionScope.withReflectiveAccess {
-                creator.expand(rootId, node, name, value, reference, startIndex, maxElements)
+                creator.expand(
+                    rootId,
+                    node,
+                    name,
+                    value,
+                    reference,
+                    startIndex,
+                    maxElements,
+                    maxRecursions,
+                    maxInitialIterableSize
+                )
             }
         } finally {
             creatorCache = creator
@@ -303,6 +317,8 @@
         private var rootId = 0L
         private var node: InspectorNode? = null
         private var parameterIndex = 0
+        private var maxRecursions = 0
+        private var maxInitialIterableSize = 0
         private var recursions = 0
         private val valueIndex = mutableListOf<Int>()
         private val valueLazyReferenceMap = IdentityHashMap<Any, MutableList<NodeParameter>>()
@@ -315,10 +331,12 @@
             node: InspectorNode,
             name: String,
             value: Any?,
-            parameterIndex: Int
+            parameterIndex: Int,
+            maxRecursions: Int,
+            maxInitialIterableSize: Int
         ): NodeParameter =
             try {
-                setup(rootId, node, parameterIndex)
+                setup(rootId, node, parameterIndex, maxRecursions, maxInitialIterableSize)
                 create(name, value) ?: createEmptyParameter(name)
             } finally {
                 setup()
@@ -331,9 +349,11 @@
             value: Any?,
             reference: NodeParameterReference,
             startIndex: Int,
-            maxElements: Int
+            maxElements: Int,
+            maxRecursions: Int,
+            maxInitialIterableSize: Int
         ): NodeParameter? {
-            setup(rootId, node, reference.parameterIndex)
+            setup(rootId, node, reference.parameterIndex, maxRecursions, maxInitialIterableSize)
             var new = Pair(name, value)
             for (i in reference.indices) {
                 new = find(new.first, new.second, i) ?: return null
@@ -358,11 +378,15 @@
         private fun setup(
             newRootId: Long = 0,
             newNode: InspectorNode? = null,
-            newParameterIndex: Int = 0
+            newParameterIndex: Int = 0,
+            maxRecursions: Int = 0,
+            maxInitialIterableSize: Int = 0
         ) {
             rootId = newRootId
             node = newNode
             parameterIndex = newParameterIndex
+            this.maxRecursions = maxRecursions
+            this.maxInitialIterableSize = maxInitialIterableSize
             recursions = 0
             valueIndex.clear()
             valueLazyReferenceMap.clear()
@@ -408,7 +432,6 @@
                 is Lambda<*> -> createFromLambda(name, value)
                 is Locale -> NodeParameter(name, ParameterType.String, value.toString())
                 is Long -> NodeParameter(name, ParameterType.Int64, value)
-                is Offset -> createFromOffset(name, value)
                 is SolidColor -> NodeParameter(name, ParameterType.Color, value.value.toArgb())
                 is String -> NodeParameter(name, ParameterType.String, value)
                 is TextUnit -> createFromTextUnit(name, value)
@@ -422,16 +445,16 @@
             name: String,
             value: Any?,
             startIndex: Int = 0,
-            maxElements: Int = maxIterable
+            maxElements: Int = maxInitialIterableSize
         ): NodeParameter? = when {
             value == null -> null
             value is Modifier -> createFromModifier(name, value)
             value is InspectableValue -> createFromInspectableValue(name, value)
-            value is Sequence<*> ->
-                createFromSequence(name, value, value, startIndex, maxElements)
+            value is Sequence<*> -> createFromSequence(name, value, value, startIndex, maxElements)
             value is Iterable<*> ->
                 createFromSequence(name, value, value.asSequence(), startIndex, maxElements)
             value.javaClass.isArray -> createFromArray(name, value, startIndex, maxElements)
+            value is Offset -> createFromOffset(name, value)
             value is Shadow -> createFromShadow(name, value)
             else -> createFromKotlinReflection(name, value)
         }
@@ -443,6 +466,7 @@
             value is Sequence<*> -> findFromSequence(value, index)
             value is Iterable<*> -> findFromSequence(value.asSequence(), index)
             value.javaClass.isArray -> findFromArray(value, index)
+            value is Offset -> findFromOffset(value, index)
             value is Shadow -> findFromShadow(value, index)
             else -> findFromKotlinReflection(value, index)
         }
@@ -450,13 +474,12 @@
         private fun createRecursively(
             name: String,
             value: Any?,
-            index: Int,
-            elementsIndex: Int
+            index: Int
         ): NodeParameter? {
             valueIndex.add(index)
             recursions++
             val parameter = create(name, value)?.apply {
-                this.index = if (index != elementsIndex) index else -1
+                this.index = index
             }
             recursions--
             valueIndex.removeLast()
@@ -486,6 +509,27 @@
         }
 
         /**
+         * Returns `true` if the value can be mapped to a [NodeParameter].
+         *
+         * Composite values should NOT be added to the [valueIndexMap] since we
+         * do not intend to include this parameter in the response.
+         */
+        private fun hasMappableValue(value: Any?): Boolean {
+            if (value == null) {
+                return false
+            }
+            if (valueIndexMap.containsKey(value)) {
+                return true
+            }
+            val remember = recursions
+            recursions = maxRecursions
+            val parameter = create("p", value)
+            recursions = remember
+            valueIndexMap.remove(value)
+            return parameter != null
+        }
+
+        /**
          * Store the reference of this [NodeParameter] by its [value]
          *
          * If the value is seen in other parameter values again, there is
@@ -602,7 +646,7 @@
                 else -> {
                     val elements = parameter.store(value).elements
                     properties.values.mapIndexedNotNullTo(elements) { index, part ->
-                        createRecursively(part.name, valueOf(part, value), index, elements.size)
+                        createRecursively(part.name, valueOf(part, value), index)
                     }
                     parameter
                 }
@@ -663,7 +707,7 @@
             }
             val elements = parameter.store(value).elements
             value.inspectableElements.mapIndexedNotNullTo(elements) { index, element ->
-                createRecursively(element.name, element.value, index, elements.size)
+                createRecursively(element.name, element.value, index)
             }
             return parameter
         }
@@ -687,20 +731,23 @@
             startIndex: Int,
             maxElements: Int
         ): NodeParameter {
-            val parameter = NodeParameter(name, ParameterType.Iterable, "")
+            val parameter = NodeParameter(name, ParameterType.Iterable, sequenceName(value))
             return when {
                 !sequence.any() -> parameter
                 !shouldRecurseDeeper() -> parameter.withChildReference(value)
                 else -> {
                     val elements = parameter.store(value).elements
-                    val rest = sequence.drop(startIndex)
-                    rest.take(maxElements)
-                        .mapIndexedNotNullTo(elements) { i, it ->
-                            val index = startIndex + i
-                            createRecursively("[$index]", it, index, startIndex + elements.size)
+                    val rest = sequence.drop(startIndex).iterator()
+                    var index = startIndex
+                    while (rest.hasNext() && elements.size < maxElements) {
+                        createRecursively("[$index]", rest.next(), index)?.let { elements.add(it) }
+                        index++
+                    }
+                    while (rest.hasNext()) {
+                        if (hasMappableValue(rest.next())) {
+                            parameter.withChildReference(value)
+                            break
                         }
-                    if (rest.drop(maxElements).any()) {
-                        parameter.withChildReference(value)
                     }
                     parameter
                 }
@@ -712,6 +759,22 @@
             return Pair("[$index]", element)
         }
 
+        private fun sequenceName(value: Any): String = when (value) {
+            is Array<*> -> "Array[${value.size}]"
+            is ByteArray -> "ByteArray[${value.size}]"
+            is IntArray -> "IntArray[${value.size}]"
+            is LongArray -> "LongArray[${value.size}]"
+            is FloatArray -> "FloatArray[${value.size}]"
+            is DoubleArray -> "DoubleArray[${value.size}]"
+            is BooleanArray -> "BooleanArray[${value.size}]"
+            is CharArray -> "CharArray[${value.size}]"
+            is List<*> -> "List[${value.size}]"
+            is Set<*> -> "Set[${value.size}]"
+            is Collection<*> -> "Collection[${value.size}]"
+            is Iterable<*> -> "Iterable"
+            else -> "Sequence"
+        }
+
         private fun createFromLambda(name: String, value: Lambda<*>): NodeParameter =
             NodeParameter(name, ParameterType.Lambda, arrayOf<Any>(value))
 
@@ -726,7 +789,7 @@
                     else -> {
                         val elements = parameter.elements
                         modifiers.mapIndexedNotNullTo(elements) { index, element ->
-                            createRecursively("", element, index, elements.size)
+                            createRecursively("", element, index)
                         }
                         parameter.store(value)
                     }
@@ -753,11 +816,20 @@
         private fun createFromOffset(name: String, value: Offset): NodeParameter {
             val parameter = NodeParameter(name, ParameterType.String, Offset::class.java.simpleName)
             val elements = parameter.elements
-            elements.add(NodeParameter("x", DimensionDp, with(density) { value.x.toDp().value }))
-            elements.add(NodeParameter("y", DimensionDp, with(density) { value.y.toDp().value }))
+            val x = with(density) { value.x.toDp().value }
+            val y = with(density) { value.y.toDp().value }
+            elements.add(NodeParameter("x", DimensionDp, x))
+            elements.add(NodeParameter("y", DimensionDp, y).apply { index = 1 })
             return parameter
         }
 
+        private fun findFromOffset(value: Offset, index: Int): Pair<String, Any?>? =
+            when (index) {
+                0 -> Pair("x", with(density) { value.x.toDp() })
+                1 -> Pair("y", with(density) { value.y.toDp() })
+                else -> null
+            }
+
         // Special handling of blurRadius: convert to dp:
         private fun createFromShadow(name: String, value: Shadow): NodeParameter? {
             val parameter = createFromKotlinReflection(name, value) ?: return null
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/proto/ComposeExtensions.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/proto/ComposeExtensions.kt
index 49db326..04f7aba 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/proto/ComposeExtensions.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/proto/ComposeExtensions.kt
@@ -92,6 +92,7 @@
 
 private fun Parameter.Builder.setValue(stringTable: StringTable, value: Any?) {
     when (type) {
+        Parameter.Type.ITERABLE,
         Parameter.Type.STRING -> {
             int32Value = stringTable.put(value as String)
         }
@@ -117,9 +118,6 @@
         Parameter.Type.RESOURCE -> setResourceType(value, stringTable)
         Parameter.Type.LAMBDA -> setFunctionType(value, stringTable)
         Parameter.Type.FUNCTION_REFERENCE -> setFunctionType(value, stringTable)
-        Parameter.Type.ITERABLE -> {
-            // TODO: b/181899238 Support size for List and Array types
-        }
         else -> error("Unknown Composable parameter type: $type")
     }
 }
diff --git a/compose/ui/ui-inspection/src/main/proto/compose_layout_inspection.proto b/compose/ui/ui-inspection/src/main/proto/compose_layout_inspection.proto
index a323d17..16c12cd 100644
--- a/compose/ui/ui-inspection/src/main/proto/compose_layout_inspection.proto
+++ b/compose/ui/ui-inspection/src/main/proto/compose_layout_inspection.proto
@@ -129,15 +129,14 @@
     repeated Parameter elements = 3;
     ParameterReference reference = 4;
 
-    // If this Parameter appears in the elements of another parameter:
-    // This index is the "natural" index of the value in the agent. If the index
-    // is identical to the elements index we do not need this value and it will
-    // be set to -1.
+    // For elements inside another Parameter instance, this index refer to the
+    // "natural" index of the parent composite value in the agent.
     //
-    // However if some of the "sibling" values in the agent are null or cannot be
-    // decomposed, those siblings are omitted from the Parameter.elements.
-    // For all subsequent parameter elements we need to have the original index in
-    // order to find the value again using the GetParameterDetailsCommand.
+    // We record this to be able to identify a reference to another parameter in
+    // the client and agent. Note that e.g:
+    //   - null elements in a List are omitted
+    // A reference will indicate the index among all values, such that we don't
+    // have to count nulls during a GetParameterDetailsCommand.
     sint32 index = 5;
 
     oneof value {
@@ -174,6 +173,14 @@
 
 // ======= COMMANDS, RESPONSES, AND EVENTS =======
 
+// Response fired when incoming command bytes cannot be parsed or handled. This may occur if a newer
+// version of the client tries to interact with an older inspector.
+message UnknownCommandResponse {
+  // The initial command bytes received that couldn't be handled. By returning this back to the
+  // client, it should be able to identify what they sent that failed on the inspector side.
+  bytes command_bytes = 1;
+}
+
 // Request all composables found under a layout tree rooted under the specified view
 message GetComposablesCommand {
    int64 root_view_id = 1;
@@ -192,6 +199,14 @@
   // As an optimization, we can skip over searching system composables if we know we don't care
   // about them.
   bool skip_system_composables = 3;
+  // Max number of recursions into nested composite parameter values.
+  // It is possible to dig in further by using: GetParameterDetailsCommand.
+  // If not specified the default is 2.
+  int32 max_recursions = 4;
+  // Max number of initial elements in an iterable such as a List/Array.
+  // It is possible to request more elements by using: GetParameterDetailsCommand.
+  // If not specified the default is 5.
+  int32 max_initial_iterable_size = 5;
 }
 
 message GetParametersResponse {
@@ -204,6 +219,8 @@
 message GetAllParametersCommand {
   int64 root_view_id = 1;
   bool skip_system_composables = 2;
+  int32 max_recursions = 3;
+  int32 max_initial_iterable_size = 4;
 }
 
 message GetAllParametersResponse {
@@ -219,6 +236,8 @@
   int32 start_index = 3;
   int32 max_elements = 4;
   bool skip_system_composables = 5;
+  int32 max_recursions = 6;
+  int32 max_initial_iterable_size = 7;
 }
 
 message GetParameterDetailsResponse {
@@ -242,5 +261,7 @@
     GetParametersResponse get_parameters_response = 2;
     GetAllParametersResponse get_all_parameters_response = 3;
     GetParameterDetailsResponse get_parameter_details_response = 4;
+
+    UnknownCommandResponse unknown_command_response = 100;
   }
 }
diff --git a/compose/ui/ui-lint/build.gradle b/compose/ui/ui-lint/build.gradle
index b76c7ac..3a00438 100644
--- a/compose/ui/ui-lint/build.gradle
+++ b/compose/ui/ui-lint/build.gradle
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+import androidx.build.BundleInsideHelper
 import androidx.build.LibraryGroups
 import androidx.build.LibraryType
 
@@ -24,6 +25,8 @@
     id("kotlin")
 }
 
+BundleInsideHelper.forInsideLintJar(project)
+
 dependencies {
     // compileOnly because we use lintChecks and it doesn't allow other types of deps
     // this ugly hack exists because of b/63873667
@@ -34,6 +37,8 @@
     }
     compileOnly(KOTLIN_STDLIB)
 
+    bundleInside(project(":compose:lint:common"))
+
     testImplementation(KOTLIN_STDLIB)
     testImplementation(LINT_CORE)
     testImplementation(LINT_TESTS)
diff --git a/compose/ui/ui-lint/lint-baseline.xml b/compose/ui/ui-lint/lint-baseline.xml
deleted file mode 100644
index 297ae16..0000000
--- a/compose/ui/ui-lint/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" version="4.2.0-beta02">
-
-</issues>
diff --git a/compose/ui/ui-lint/src/main/java/androidx/compose/ui/lint/ModifierDeclarationDetector.kt b/compose/ui/ui-lint/src/main/java/androidx/compose/ui/lint/ModifierDeclarationDetector.kt
index 8f63900..7a8eb63 100644
--- a/compose/ui/ui-lint/src/main/java/androidx/compose/ui/lint/ModifierDeclarationDetector.kt
+++ b/compose/ui/ui-lint/src/main/java/androidx/compose/ui/lint/ModifierDeclarationDetector.kt
@@ -18,6 +18,8 @@
 
 package androidx.compose.ui.lint
 
+import androidx.compose.lint.Names
+import androidx.compose.lint.isComposable
 import androidx.compose.ui.lint.ModifierDeclarationDetector.Companion.ComposableModifierFactory
 import androidx.compose.ui.lint.ModifierDeclarationDetector.Companion.ModifierFactoryReturnType
 import com.android.tools.lint.client.api.UElementHandler
@@ -49,9 +51,11 @@
 /**
  * [Detector] that checks functions returning Modifiers for consistency with guidelines.
  *
- * - Modifier factory functions must return Modifier as their type, and not a subclass of Modifier
- * - Modifier factory functions must be defined as an extension on Modifier to allow fluent chaining
- * - Modifier factory functions must not be marked as @Composable, and should use `composed` instead
+ * - Modifier factory functions should return Modifier as their type, and not a subclass of Modifier
+ * - Modifier factory functions should be defined as an extension on Modifier to allow fluent
+ * chaining
+ * - Modifier factory functions should not be marked as @Composable, and should use `composed`
+ * instead
  */
 class ModifierDeclarationDetector : Detector(), SourceCodeScanner {
     override fun getApplicableUastTypes() = listOf(UMethod::class.java)
@@ -62,7 +66,7 @@
             val returnType = node.returnType ?: return
 
             // Ignore functions that do not return Modifier or something implementing Modifier
-            if (!InheritanceUtil.isInheritor(returnType, ModifierFqn)) return
+            if (!InheritanceUtil.isInheritor(returnType, Names.Ui.Modifier.javaFqn)) return
 
             val source = node.sourcePsi
 
@@ -99,7 +103,7 @@
                 "androidx.compose.ui.composed {} in their implementation instead of being marked " +
                 "as @Composable. This allows Modifiers to be referenced in top level variables " +
                 "and constructed outside of the composition.",
-            Category.CORRECTNESS, 3, Severity.ERROR,
+            Category.CORRECTNESS, 3, Severity.WARNING,
             Implementation(
                 ModifierDeclarationDetector::class.java,
                 EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
@@ -108,10 +112,10 @@
 
         val ModifierFactoryReturnType = Issue.create(
             "ModifierFactoryReturnType",
-            "Modifier factory functions must return Modifier",
-            "Modifier factory functions must return Modifier as their type, and not a " +
+            "Modifier factory functions should return Modifier",
+            "Modifier factory functions should return Modifier as their type, and not a " +
                 "subtype of Modifier (such as Modifier.Element).",
-            Category.CORRECTNESS, 3, Severity.ERROR,
+            Category.CORRECTNESS, 3, Severity.WARNING,
             Implementation(
                 ModifierDeclarationDetector::class.java,
                 EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
@@ -120,10 +124,10 @@
 
         val ModifierFactoryExtensionFunction = Issue.create(
             "ModifierFactoryExtensionFunction",
-            "Modifier factory functions must be extensions on Modifier",
-            "Modifier factory functions must be defined as extension functions on" +
+            "Modifier factory functions should be extensions on Modifier",
+            "Modifier factory functions should be defined as extension functions on" +
                 " Modifier to allow modifiers to be fluently chained.",
-            Category.CORRECTNESS, 3, Severity.ERROR,
+            Category.CORRECTNESS, 3, Severity.WARNING,
             Implementation(
                 ModifierDeclarationDetector::class.java,
                 EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
@@ -183,7 +187,7 @@
             ModifierDeclarationDetector.ModifierFactoryExtensionFunction,
             this,
             context.getNameLocation(this),
-            "Modifier factory functions must be extensions on Modifier",
+            "Modifier factory functions should be extensions on Modifier",
             lintFix
         )
     }
@@ -205,7 +209,7 @@
                 .name("Add Modifier receiver")
                 .range(context.getLocation(source))
                 .text(name)
-                .with("$ModifierShortName.$name")
+                .with("${Names.Ui.Modifier.shortName}.$name")
                 .autoFix()
                 .build()
         )
@@ -218,10 +222,10 @@
             )?.qualifiedName
         val hasModifierReceiver = if (receiverFqn != null) {
             // If we could resolve the class, match fqn
-            receiverFqn == ModifierFqn
+            receiverFqn == Names.Ui.Modifier.javaFqn
         } else {
             // Otherwise just try and match the short names
-            receiverShortName == ModifierShortName
+            receiverShortName == Names.Ui.Modifier.shortName
         }
         if (!hasModifierReceiver) {
             report(
@@ -230,7 +234,7 @@
                     .name("Change receiver to Modifier")
                     .range(context.getLocation(source))
                     .text(receiverShortName)
-                    .with(ModifierShortName)
+                    .with(Names.Ui.Modifier.shortName)
                     .autoFix()
                     .build()
             )
@@ -247,12 +251,12 @@
             ModifierFactoryReturnType,
             this,
             context.getNameLocation(this),
-            "Modifier factory functions must have a return type of Modifier",
+            "Modifier factory functions should have a return type of Modifier",
             lintFix
         )
     }
 
-    if (returnType.canonicalText == ModifierFqn) return
+    if (returnType.canonicalText == Names.Ui.Modifier.javaFqn) return
 
     val source = sourcePsi
     if (source is KtCallableDeclaration && source.returnTypeString != null) {
@@ -264,7 +268,7 @@
                 .name("Change return type to Modifier")
                 .range(context.getLocation(this))
                 .text(source.returnTypeString)
-                .with(ModifierShortName)
+                .with(Names.Ui.Modifier.shortName)
                 .autoFix()
                 .build()
         )
@@ -282,7 +286,7 @@
                     .name("Change return type to Modifier")
                     .range(context.getLocation(this))
                     .text(getterReturnType)
-                    .with(ModifierShortName)
+                    .with(Names.Ui.Modifier.shortName)
                     .autoFix()
                     .build()
             )
@@ -299,7 +303,7 @@
                     .name("Change return type to Modifier")
                     .range(context.getLocation(source.property))
                     .text(propertyType)
-                    .with(ModifierShortName)
+                    .with(Names.Ui.Modifier.shortName)
                     .autoFix()
                     .build()
             )
@@ -316,7 +320,7 @@
                 .name("Add explicit Modifier return type")
                 .range(context.getLocation(this))
                 .pattern("[ \\t\\n]+=")
-                .with(": $ModifierShortName =")
+                .with(": ${Names.Ui.Modifier.shortName} =")
                 .autoFix()
                 .build()
         )
diff --git a/compose/ui/ui-lint/src/main/java/androidx/compose/ui/lint/ModifierParameterDetector.kt b/compose/ui/ui-lint/src/main/java/androidx/compose/ui/lint/ModifierParameterDetector.kt
index 99ba2af1..a0f2fa1 100644
--- a/compose/ui/ui-lint/src/main/java/androidx/compose/ui/lint/ModifierParameterDetector.kt
+++ b/compose/ui/ui-lint/src/main/java/androidx/compose/ui/lint/ModifierParameterDetector.kt
@@ -18,6 +18,9 @@
 
 package androidx.compose.ui.lint
 
+import androidx.compose.lint.Names
+import androidx.compose.lint.isComposable
+import androidx.compose.lint.returnsUnit
 import com.android.tools.lint.client.api.UElementHandler
 import com.android.tools.lint.detector.api.Category
 import com.android.tools.lint.detector.api.Detector
@@ -28,7 +31,6 @@
 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 com.intellij.psi.PsiType
 import com.intellij.psi.util.InheritanceUtil
 import org.jetbrains.kotlin.psi.KtNameReferenceExpression
 import org.jetbrains.kotlin.psi.KtParameter
@@ -56,11 +58,11 @@
             if (!node.isComposable) return
 
             // Ignore non-unit composable functions
-            if (node.returnType != PsiType.VOID) return
+            if (!node.returnsUnit) return
 
             val modifierParameter = node.uastParameters.firstOrNull { parameter ->
                 parameter.sourcePsi is KtParameter &&
-                    InheritanceUtil.isInheritor(parameter.type, ModifierFqn)
+                    InheritanceUtil.isInheritor(parameter.type, Names.Ui.Modifier.javaFqn)
             } ?: return
 
             // Need to strongly type this or else Kotlinc cannot resolve overloads for
@@ -69,12 +71,14 @@
 
             val source = modifierParameter.sourcePsi as KtParameter
 
+            val modifierName = Names.Ui.Modifier.shortName
+
             if (modifierParameter.name != ModifierParameterName) {
                 context.report(
                     ModifierParameter,
                     node,
                     context.getNameLocation(modifierParameterElement),
-                    "$ModifierShortName parameter should be named $ModifierParameterName",
+                    "$modifierName parameter should be named $ModifierParameterName",
                     LintFix.create()
                         .replace()
                         .name("Change name to $ModifierParameterName")
@@ -85,18 +89,18 @@
                 )
             }
 
-            if (modifierParameter.type.canonicalText != ModifierFqn) {
+            if (modifierParameter.type.canonicalText != Names.Ui.Modifier.javaFqn) {
                 context.report(
                     ModifierParameter,
                     node,
                     context.getNameLocation(modifierParameterElement),
-                    "$ModifierShortName parameter should have a type of $ModifierShortName",
+                    "$modifierName parameter should have a type of $modifierName",
                     LintFix.create()
                         .replace()
                         .range(context.getLocation(modifierParameterElement))
-                        .name("Change type to $ModifierShortName")
+                        .name("Change type to $modifierName")
                         .text(source.typeReference!!.text)
-                        .with(ModifierShortName)
+                        .with(modifierName)
                         .autoFix()
                         .build()
                 )
@@ -107,19 +111,19 @@
                 // If the default value is not a reference expression, then it isn't `Modifier`
                 // anyway and we can just report an error
                 val referenceExpression = source.defaultValue as? KtNameReferenceExpression
-                if (referenceExpression?.getReferencedName() != ModifierShortName) {
+                if (referenceExpression?.getReferencedName() != modifierName) {
                     context.report(
                         ModifierParameter,
                         node,
                         context.getNameLocation(modifierParameterElement),
-                        "Optional $ModifierShortName parameter should have a default value " +
-                            "of `$ModifierShortName`",
+                        "Optional $modifierName parameter should have a default value " +
+                            "of `$modifierName`",
                         LintFix.create()
                             .replace()
                             .range(context.getLocation(modifierParameterElement))
-                            .name("Change default value to $ModifierShortName")
+                            .name("Change default value to $modifierName")
                             .text(defaultValue.text)
-                            .with(ModifierShortName)
+                            .with(modifierName)
                             .autoFix()
                             .build()
                     )
@@ -133,7 +137,7 @@
                         ModifierParameter,
                         node,
                         context.getNameLocation(modifierParameterElement),
-                        "$ModifierShortName parameter should be the first optional parameter",
+                        "$modifierName parameter should be the first optional parameter",
                         // Hard to make a lint fix for this and keep parameter formatting, so
                         // ignore it
                     )
@@ -148,11 +152,12 @@
             "Guidelines for Modifier parameters in a Composable function",
             "The first (or only) Modifier parameter in a Composable function should follow the " +
                 "following rules:" +
-                "- Be named `$ModifierParameterName`" +
-                "- Have a type of `$ModifierShortName`" +
-                "- Either have no default value, or have a default value of `$ModifierShortName`" +
-                "- If optional, be the first optional parameter in the parameter list",
-            Category.CORRECTNESS, 3, Severity.ERROR,
+                "\n- Be named `$ModifierParameterName`" +
+                "\n- Have a type of `${Names.Ui.Modifier.shortName}`" +
+                "\n- Either have no default value, or have a default value of " +
+                "`${Names.Ui.Modifier.shortName}`" +
+                "\n- If optional, be the first optional parameter in the parameter list",
+            Category.CORRECTNESS, 3, Severity.WARNING,
             Implementation(
                 ModifierParameterDetector::class.java,
                 EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
@@ -161,4 +166,4 @@
     }
 }
 
-private val ModifierParameterName = ModifierShortName.decapitalize(Locale.ROOT)
+private val ModifierParameterName = Names.Ui.Modifier.shortName.decapitalize(Locale.ROOT)
diff --git a/compose/ui/ui-lint/src/main/java/androidx/compose/ui/lint/Utils.kt b/compose/ui/ui-lint/src/main/java/androidx/compose/ui/lint/Utils.kt
deleted file mode 100644
index 89e8f8f..0000000
--- a/compose/ui/ui-lint/src/main/java/androidx/compose/ui/lint/Utils.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.lint
-
-import org.jetbrains.uast.UMethod
-
-// TODO: KotlinUMethodWithFakeLightDelegate.hasAnnotation() returns null for some reason, so just
-// look at the annotations directly
-// TODO: annotations is deprecated but the replacement uAnnotations isn't available on the
-// version of lint / uast we compile against
-@Suppress("DEPRECATION")
-val UMethod.isComposable get() = annotations.any { it.qualifiedName == ComposableFqn }
-
-const val ComposableFqn = "androidx.compose.runtime.Composable"
-
-const val ModifierFqn = "androidx.compose.ui.Modifier"
-val ModifierShortName = ModifierFqn.split(".").last()
\ No newline at end of file
diff --git a/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/ModifierDeclarationDetectorTest.kt b/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/ModifierDeclarationDetectorTest.kt
index d4333ae..33aac1f 100644
--- a/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/ModifierDeclarationDetectorTest.kt
+++ b/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/ModifierDeclarationDetectorTest.kt
@@ -18,6 +18,7 @@
 
 package androidx.compose.ui.lint
 
+import androidx.compose.lint.Stubs
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
@@ -57,15 +58,15 @@
                 }
             """
             ),
-            modifierStub
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
                 """
-src/androidx/compose/ui/foo/TestModifier.kt:8: Error: Modifier factory functions must have a return type of Modifier [ModifierFactoryReturnType]
+src/androidx/compose/ui/foo/TestModifier.kt:8: Warning: Modifier factory functions should have a return type of Modifier [ModifierFactoryReturnType]
                 fun Modifier.fooModifier(): Modifier.Element {
                              ~~~~~~~~~~~
-1 errors, 0 warnings
+0 errors, 1 warnings
             """
             )
             .expectFixDiffs(
@@ -100,21 +101,21 @@
                 val Modifier.fooModifier3: Modifier.Element get() = TestModifier
             """
             ),
-            modifierStub
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
                 """
-src/androidx/compose/ui/foo/TestModifier.kt:8: Error: Modifier factory functions must have a return type of Modifier [ModifierFactoryReturnType]
+src/androidx/compose/ui/foo/TestModifier.kt:8: Warning: Modifier factory functions should have a return type of Modifier [ModifierFactoryReturnType]
                 val Modifier.fooModifier get(): Modifier.Element {
                                          ~~~
-src/androidx/compose/ui/foo/TestModifier.kt:12: Error: Modifier factory functions must have a return type of Modifier [ModifierFactoryReturnType]
+src/androidx/compose/ui/foo/TestModifier.kt:12: Warning: Modifier factory functions should have a return type of Modifier [ModifierFactoryReturnType]
                 val Modifier.fooModifier2: Modifier.Element get() {
                                                             ~~~
-src/androidx/compose/ui/foo/TestModifier.kt:16: Error: Modifier factory functions must have a return type of Modifier [ModifierFactoryReturnType]
+src/androidx/compose/ui/foo/TestModifier.kt:16: Warning: Modifier factory functions should have a return type of Modifier [ModifierFactoryReturnType]
                 val Modifier.fooModifier3: Modifier.Element get() = TestModifier
                                                             ~~~
-3 errors, 0 warnings
+0 errors, 3 warnings
             """
             )
             .expectFixDiffs(
@@ -149,15 +150,15 @@
                 fun Modifier.fooModifier() = TestModifier
             """
             ),
-            modifierStub
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
                 """
-src/androidx/compose/ui/foo/TestModifier.kt:8: Error: Modifier factory functions must have a return type of Modifier [ModifierFactoryReturnType]
+src/androidx/compose/ui/foo/TestModifier.kt:8: Warning: Modifier factory functions should have a return type of Modifier [ModifierFactoryReturnType]
                 fun Modifier.fooModifier() = TestModifier
                              ~~~~~~~~~~~
-1 errors, 0 warnings
+0 errors, 1 warnings
             """
             )
             .expectFixDiffs(
@@ -184,15 +185,15 @@
                 val Modifier.fooModifier get() = TestModifier
             """
             ),
-            modifierStub
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
                 """
-src/androidx/compose/ui/foo/TestModifier.kt:8: Error: Modifier factory functions must have a return type of Modifier [ModifierFactoryReturnType]
+src/androidx/compose/ui/foo/TestModifier.kt:8: Warning: Modifier factory functions should have a return type of Modifier [ModifierFactoryReturnType]
                 val Modifier.fooModifier get() = TestModifier
                                          ~~~
-1 errors, 0 warnings
+0 errors, 1 warnings
             """
             )
             .expectFixDiffs(
@@ -221,15 +222,15 @@
                 }
             """
             ),
-            modifierStub
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
                 """
-src/androidx/compose/ui/foo/TestModifier.kt:8: Error: Modifier factory functions must have a return type of Modifier [ModifierFactoryReturnType]
+src/androidx/compose/ui/foo/TestModifier.kt:8: Warning: Modifier factory functions should have a return type of Modifier [ModifierFactoryReturnType]
                 fun Modifier.fooModifier(): TestModifier {
                              ~~~~~~~~~~~
-1 errors, 0 warnings
+0 errors, 1 warnings
             """
             )
             .expectFixDiffs(
@@ -276,7 +277,7 @@
                 }
             """
             ),
-            modifierStub
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expectClean()
@@ -311,7 +312,7 @@
                 }
             """
             ),
-            modifierStub
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expectClean()
@@ -343,24 +344,24 @@
                 val fooModifier3: Modifier get() = TestModifier
             """
             ),
-            modifierStub
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
                 """
-src/androidx/compose/ui/foo/TestModifier.kt:8: Error: Modifier factory functions must be extensions on Modifier [ModifierFactoryExtensionFunction]
+src/androidx/compose/ui/foo/TestModifier.kt:8: Warning: Modifier factory functions should be extensions on Modifier [ModifierFactoryExtensionFunction]
                 fun fooModifier(): Modifier {
                     ~~~~~~~~~~~
-src/androidx/compose/ui/foo/TestModifier.kt:12: Error: Modifier factory functions must be extensions on Modifier [ModifierFactoryExtensionFunction]
+src/androidx/compose/ui/foo/TestModifier.kt:12: Warning: Modifier factory functions should be extensions on Modifier [ModifierFactoryExtensionFunction]
                 val fooModifier get(): Modifier {
                                 ~~~
-src/androidx/compose/ui/foo/TestModifier.kt:16: Error: Modifier factory functions must be extensions on Modifier [ModifierFactoryExtensionFunction]
+src/androidx/compose/ui/foo/TestModifier.kt:16: Warning: Modifier factory functions should be extensions on Modifier [ModifierFactoryExtensionFunction]
                 val fooModifier2: Modifier get() {
                                            ~~~
-src/androidx/compose/ui/foo/TestModifier.kt:20: Error: Modifier factory functions must be extensions on Modifier [ModifierFactoryExtensionFunction]
+src/androidx/compose/ui/foo/TestModifier.kt:20: Warning: Modifier factory functions should be extensions on Modifier [ModifierFactoryExtensionFunction]
                 val fooModifier3: Modifier get() = TestModifier
                                            ~~~
-4 errors, 0 warnings
+0 errors, 4 warnings
             """
             )
             .expectFixDiffs(
@@ -411,24 +412,24 @@
                 val TestModifier.fooModifier3: Modifier get() = TestModifier
             """
             ),
-            modifierStub
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
                 """
-src/androidx/compose/ui/foo/TestModifier.kt:8: Error: Modifier factory functions must be extensions on Modifier [ModifierFactoryExtensionFunction]
+src/androidx/compose/ui/foo/TestModifier.kt:8: Warning: Modifier factory functions should be extensions on Modifier [ModifierFactoryExtensionFunction]
                 fun TestModifier.fooModifier(): Modifier {
                                  ~~~~~~~~~~~
-src/androidx/compose/ui/foo/TestModifier.kt:12: Error: Modifier factory functions must be extensions on Modifier [ModifierFactoryExtensionFunction]
+src/androidx/compose/ui/foo/TestModifier.kt:12: Warning: Modifier factory functions should be extensions on Modifier [ModifierFactoryExtensionFunction]
                 val TestModifier.fooModifier get(): Modifier {
                                              ~~~
-src/androidx/compose/ui/foo/TestModifier.kt:16: Error: Modifier factory functions must be extensions on Modifier [ModifierFactoryExtensionFunction]
+src/androidx/compose/ui/foo/TestModifier.kt:16: Warning: Modifier factory functions should be extensions on Modifier [ModifierFactoryExtensionFunction]
                 val TestModifier.fooModifier2: Modifier get() {
                                                         ~~~
-src/androidx/compose/ui/foo/TestModifier.kt:20: Error: Modifier factory functions must be extensions on Modifier [ModifierFactoryExtensionFunction]
+src/androidx/compose/ui/foo/TestModifier.kt:20: Warning: Modifier factory functions should be extensions on Modifier [ModifierFactoryExtensionFunction]
                 val TestModifier.fooModifier3: Modifier get() = TestModifier
                                                         ~~~
-4 errors, 0 warnings
+0 errors, 4 warnings
             """
             )
             .expectFixDiffs(
@@ -487,25 +488,25 @@
                 val Modifier.fooModifier4: Modifier get() = TestModifier(someComposableCall(3))
             """
             ),
-            modifierStub,
-            composableStub
+            kotlin(Stubs.Modifier),
+            kotlin(Stubs.Composable)
         )
             .run()
             .expect(
                 """
-src/androidx/compose/ui/foo/TestModifier.kt:13: Error: Modifier factory functions should not be marked as @Composable, and should use composed instead [ComposableModifierFactory]
+src/androidx/compose/ui/foo/TestModifier.kt:13: Warning: Modifier factory functions should not be marked as @Composable, and should use composed instead [ComposableModifierFactory]
                 fun Modifier.fooModifier1(): Modifier {
                              ~~~~~~~~~~~~
-src/androidx/compose/ui/foo/TestModifier.kt:19: Error: Modifier factory functions should not be marked as @Composable, and should use composed instead [ComposableModifierFactory]
+src/androidx/compose/ui/foo/TestModifier.kt:19: Warning: Modifier factory functions should not be marked as @Composable, and should use composed instead [ComposableModifierFactory]
                 fun Modifier.fooModifier2(): Modifier = TestModifier(someComposableCall(3))
                              ~~~~~~~~~~~~
-src/androidx/compose/ui/foo/TestModifier.kt:22: Error: Modifier factory functions should not be marked as @Composable, and should use composed instead [ComposableModifierFactory]
+src/androidx/compose/ui/foo/TestModifier.kt:22: Warning: Modifier factory functions should not be marked as @Composable, and should use composed instead [ComposableModifierFactory]
                 val Modifier.fooModifier3: Modifier get() {
                                                     ~~~
-src/androidx/compose/ui/foo/TestModifier.kt:28: Error: Modifier factory functions should not be marked as @Composable, and should use composed instead [ComposableModifierFactory]
+src/androidx/compose/ui/foo/TestModifier.kt:28: Warning: Modifier factory functions should not be marked as @Composable, and should use composed instead [ComposableModifierFactory]
                 val Modifier.fooModifier4: Modifier get() = TestModifier(someComposableCall(3))
                                                     ~~~
-4 errors, 0 warnings
+0 errors, 4 warnings
             """
             )
             .expectFixDiffs(
@@ -556,7 +557,7 @@
                 }
             """
             ),
-            modifierStub
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expectClean()
diff --git a/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/ModifierParameterDetectorTest.kt b/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/ModifierParameterDetectorTest.kt
index d941ec5..25b8f24 100644
--- a/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/ModifierParameterDetectorTest.kt
+++ b/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/ModifierParameterDetectorTest.kt
@@ -18,6 +18,7 @@
 
 package androidx.compose.ui.lint
 
+import androidx.compose.lint.Stubs
 import com.android.tools.lint.checks.infrastructure.LintDetectorTest
 import com.android.tools.lint.detector.api.Detector
 import com.android.tools.lint.detector.api.Issue
@@ -58,16 +59,16 @@
                 ) {}
             """
             ),
-            composableStub,
-            modifierStub
+            kotlin(Stubs.Composable),
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
                 """
-src/androidx/compose/ui/foo/test.kt:10: Error: Modifier parameter should be named modifier [ModifierParameter]
+src/androidx/compose/ui/foo/test.kt:10: Warning: Modifier parameter should be named modifier [ModifierParameter]
                     buttonModifier: Modifier = Modifier,
                     ~~~~~~~~~~~~~~
-1 errors, 0 warnings
+0 errors, 1 warnings
             """
             )
             .expectFixDiffs(
@@ -99,16 +100,16 @@
                 ) {}
             """
             ),
-            composableStub,
-            modifierStub
+            kotlin(Stubs.Composable),
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
                 """
-src/androidx/compose/ui/foo/test.kt:10: Error: Modifier parameter should have a type of Modifier [ModifierParameter]
+src/androidx/compose/ui/foo/test.kt:10: Warning: Modifier parameter should have a type of Modifier [ModifierParameter]
                     modifier: Modifier.Element,
                     ~~~~~~~~
-1 errors, 0 warnings
+0 errors, 1 warnings
             """
             )
             .expectFixDiffs(
@@ -142,16 +143,16 @@
                 ) {}
             """
             ),
-            composableStub,
-            modifierStub
+            kotlin(Stubs.Composable),
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
                 """
-src/androidx/compose/ui/foo/TestModifier.kt:12: Error: Optional Modifier parameter should have a default value of Modifier [ModifierParameter]
+src/androidx/compose/ui/foo/TestModifier.kt:12: Warning: Optional Modifier parameter should have a default value of Modifier [ModifierParameter]
                     modifier: Modifier = TestModifier,
                     ~~~~~~~~
-1 errors, 0 warnings
+0 errors, 1 warnings
             """
             )
             .expectFixDiffs(
@@ -183,16 +184,16 @@
                 ) {}
             """
             ),
-            composableStub,
-            modifierStub
+            kotlin(Stubs.Composable),
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
                 """
-src/androidx/compose/ui/foo/test.kt:11: Error: Modifier parameter should be the first optional parameter [ModifierParameter]
+src/androidx/compose/ui/foo/test.kt:11: Warning: Modifier parameter should be the first optional parameter [ModifierParameter]
                     modifier: Modifier = Modifier,
                     ~~~~~~~~
-1 errors, 0 warnings
+0 errors, 1 warnings
             """
             )
     }
@@ -218,25 +219,25 @@
                 ) {}
             """
             ),
-            composableStub,
-            modifierStub
+            kotlin(Stubs.Composable),
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
                 """
-src/androidx/compose/ui/foo/TestModifier.kt:13: Error: Modifier parameter should be named modifier [ModifierParameter]
+src/androidx/compose/ui/foo/TestModifier.kt:13: Warning: Modifier parameter should be named modifier [ModifierParameter]
                     buttonModifier: Modifier.Element = TestModifier,
                     ~~~~~~~~~~~~~~
-src/androidx/compose/ui/foo/TestModifier.kt:13: Error: Modifier parameter should be the first optional parameter [ModifierParameter]
+src/androidx/compose/ui/foo/TestModifier.kt:13: Warning: Modifier parameter should be the first optional parameter [ModifierParameter]
                     buttonModifier: Modifier.Element = TestModifier,
                     ~~~~~~~~~~~~~~
-src/androidx/compose/ui/foo/TestModifier.kt:13: Error: Modifier parameter should have a type of Modifier [ModifierParameter]
+src/androidx/compose/ui/foo/TestModifier.kt:13: Warning: Modifier parameter should have a type of Modifier [ModifierParameter]
                     buttonModifier: Modifier.Element = TestModifier,
                     ~~~~~~~~~~~~~~
-src/androidx/compose/ui/foo/TestModifier.kt:13: Error: Optional Modifier parameter should have a default value of Modifier [ModifierParameter]
+src/androidx/compose/ui/foo/TestModifier.kt:13: Warning: Optional Modifier parameter should have a default value of Modifier [ModifierParameter]
                     buttonModifier: Modifier.Element = TestModifier,
                     ~~~~~~~~~~~~~~
-4 errors, 0 warnings
+0 errors, 4 warnings
             """
             )
             .expectFixDiffs(
@@ -273,8 +274,8 @@
                 ) {}
             """
             ),
-            composableStub,
-            modifierStub
+            kotlin(Stubs.Composable),
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expectClean()
@@ -302,8 +303,8 @@
                 ) {}
             """
             ),
-            composableStub,
-            modifierStub
+            kotlin(Stubs.Composable),
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expectClean()
@@ -336,8 +337,8 @@
                 ) {}
             """
             ),
-            composableStub,
-            modifierStub
+            kotlin(Stubs.Composable),
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expectClean()
diff --git a/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/Stubs.kt b/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/Stubs.kt
deleted file mode 100644
index abe1e46..0000000
--- a/compose/ui/ui-lint/src/test/java/androidx/compose/ui/lint/Stubs.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:Suppress("UnstableApiUsage")
-
-package androidx.compose.ui.lint
-
-import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-
-val modifierStub: LintDetectorTest.TestFile = LintDetectorTest.kotlin(
-    """
-            package androidx.compose.ui
-
-            interface Modifier {
-                interface Element : Modifier
-                companion object : Modifier
-            }
-        """
-)
-
-val composableStub: LintDetectorTest.TestFile = LintDetectorTest.kotlin(
-    """
-            package androidx.compose.runtime
-
-            @MustBeDocumented
-            @Retention(AnnotationRetention.BINARY)
-            @Target(
-                AnnotationTarget.FUNCTION,
-                AnnotationTarget.TYPE,
-                AnnotationTarget.TYPE_PARAMETER,
-                AnnotationTarget.PROPERTY
-            )
-            annotation class Composable
-        """
-)
diff --git a/compose/ui/ui-test-font/lint-baseline.xml b/compose/ui/ui-test-font/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/ui/ui-test-font/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/ui/ui-test-junit4/build.gradle b/compose/ui/ui-test-junit4/build.gradle
index aa26329..1130408 100644
--- a/compose/ui/ui-test-junit4/build.gradle
+++ b/compose/ui/ui-test-junit4/build.gradle
@@ -17,7 +17,7 @@
 
 import androidx.build.AndroidXUiPlugin
 import androidx.build.LibraryGroups
-import androidx.build.Publish
+import androidx.build.LibraryType
 
 import static androidx.build.dependencies.DependenciesKt.*
 
@@ -129,7 +129,7 @@
 
 androidx {
     name = "Compose Testing for JUnit4"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.UI
     inceptionYear = "2020"
     description = "Compose testing integration with JUnit4"
diff --git a/compose/ui/ui-test-junit4/lint-baseline.xml b/compose/ui/ui-test-junit4/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/ui/ui-test-junit4/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/ui/ui-test-manifest/OWNERS b/compose/ui/ui-test-manifest/OWNERS
new file mode 100644
index 0000000..42abc4e
--- /dev/null
+++ b/compose/ui/ui-test-manifest/OWNERS
@@ -0,0 +1,2 @@
[email protected]
[email protected]
diff --git a/compose/ui/ui-test-manifest/api/1.0.0-beta02.txt b/compose/ui/ui-test-manifest/api/1.0.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/compose/ui/ui-test-manifest/api/1.0.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/compose/ui/ui-test-manifest/api/current.txt b/compose/ui/ui-test-manifest/api/current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/compose/ui/ui-test-manifest/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/compose/ui/ui-test-manifest/api/public_plus_experimental_1.0.0-beta02.txt b/compose/ui/ui-test-manifest/api/public_plus_experimental_1.0.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/compose/ui/ui-test-manifest/api/public_plus_experimental_1.0.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/compose/ui/ui-test-manifest/api/public_plus_experimental_current.txt b/compose/ui/ui-test-manifest/api/public_plus_experimental_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/compose/ui/ui-test-manifest/api/public_plus_experimental_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/compose/ui/ui-test-manifest/api/res-1.0.0-beta02.txt b/compose/ui/ui-test-manifest/api/res-1.0.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/compose/ui/ui-test-manifest/api/res-1.0.0-beta02.txt
diff --git a/compose/ui/ui-test-manifest/api/res-current.txt b/compose/ui/ui-test-manifest/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/compose/ui/ui-test-manifest/api/res-current.txt
diff --git a/compose/ui/ui-test-manifest/api/restricted_1.0.0-beta02.txt b/compose/ui/ui-test-manifest/api/restricted_1.0.0-beta02.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/compose/ui/ui-test-manifest/api/restricted_1.0.0-beta02.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/compose/ui/ui-test-manifest/api/restricted_current.txt b/compose/ui/ui-test-manifest/api/restricted_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/compose/ui/ui-test-manifest/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/compose/ui/ui-test-manifest/build.gradle b/compose/ui/ui-test-manifest/build.gradle
new file mode 100644
index 0000000..02fdfd7
--- /dev/null
+++ b/compose/ui/ui-test-manifest/build.gradle
@@ -0,0 +1,36 @@
+/*
+ * 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.LibraryGroups
+import androidx.build.LibraryType
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("AndroidXUiPlugin")
+}
+
+dependencies {
+    api("androidx.activity:activity:1.2.0")
+}
+
+androidx {
+    name = "Compose Testing manifest dependency"
+    type = LibraryType.PUBLISHED_LIBRARY
+    mavenGroup = LibraryGroups.Compose.UI
+    inceptionYear = "2021"
+    description = "Compose testing library that should be added as a debugImplementation dependency to add properties to the debug manifest necessary for testing an application"
+}
diff --git a/compose/ui/ui-test-manifest/integration-tests/testapp/OWNERS b/compose/ui/ui-test-manifest/integration-tests/testapp/OWNERS
new file mode 100644
index 0000000..42abc4e
--- /dev/null
+++ b/compose/ui/ui-test-manifest/integration-tests/testapp/OWNERS
@@ -0,0 +1,2 @@
[email protected]
[email protected]
diff --git a/compose/ui/ui-test-manifest/integration-tests/testapp/build.gradle b/compose/ui/ui-test-manifest/integration-tests/testapp/build.gradle
new file mode 100644
index 0000000..ef1ee31
--- /dev/null
+++ b/compose/ui/ui-test-manifest/integration-tests/testapp/build.gradle
@@ -0,0 +1,39 @@
+/*
+ * 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 static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+    id("AndroidXPlugin")
+    id("AndroidXUiPlugin")
+    id("com.android.application")
+    id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+    kotlinPlugin(project(":compose:compiler:compiler"))
+
+    debugImplementation(project(":compose:ui:ui-test-manifest"))
+
+    androidTestImplementation(project(":compose:ui:ui-test-junit4"))
+    androidTestImplementation(ANDROIDX_TEST_RUNNER)
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 21
+    }
+}
diff --git a/compose/ui/ui-test-manifest/integration-tests/testapp/src/androidTest/java/androidx/compose/ui/test/manifest/integration/testapp/ComponentActivityLaunchesTest.kt b/compose/ui/ui-test-manifest/integration-tests/testapp/src/androidTest/java/androidx/compose/ui/test/manifest/integration/testapp/ComponentActivityLaunchesTest.kt
new file mode 100644
index 0000000..a88b849
--- /dev/null
+++ b/compose/ui/ui-test-manifest/integration-tests/testapp/src/androidTest/java/androidx/compose/ui/test/manifest/integration/testapp/ComponentActivityLaunchesTest.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.test.manifest.integration.testapp
+
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class ComponentActivityLaunchesTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun test() {
+        rule.setContent {}
+        // Test does not crash and does not time out
+    }
+}
diff --git a/compose/material/material/integration-tests/material-studies/src/main/AndroidManifest.xml b/compose/ui/ui-test-manifest/integration-tests/testapp/src/main/AndroidManifest.xml
similarity index 61%
rename from compose/material/material/integration-tests/material-studies/src/main/AndroidManifest.xml
rename to compose/ui/ui-test-manifest/integration-tests/testapp/src/main/AndroidManifest.xml
index cba5993..8f6a9da 100644
--- a/compose/material/material/integration-tests/material-studies/src/main/AndroidManifest.xml
+++ b/compose/ui/ui-test-manifest/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -14,15 +14,9 @@
   ~ limitations under the License
   -->
 
-<manifest
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        package="androidx.compose.material.studies">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="androidx.compose.ui.test.manifest.integration.testapp">
 
-    <application>
-        <activity android:name=".rally.RallyActivity"
-                  android:theme="@android:style/Theme.NoTitleBar"
-                  android:configChanges="orientation|screenSize"
-                  android:label="Rally">
-        </activity>
-    </application>
+    <application android:label="ui-test-manifest test app" />
+
 </manifest>
diff --git a/compose/material/material/integration-tests/material-studies/src/main/res/values/strings.xml b/compose/ui/ui-test-manifest/src/main/AndroidManifest.xml
similarity index 67%
rename from compose/material/material/integration-tests/material-studies/src/main/res/values/strings.xml
rename to compose/ui/ui-test-manifest/src/main/AndroidManifest.xml
index 7df5dd7b..833d681 100644
--- a/compose/material/material/integration-tests/material-studies/src/main/res/values/strings.xml
+++ b/compose/ui/ui-test-manifest/src/main/AndroidManifest.xml
@@ -1,4 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?><!--
+<?xml version="1.0" encoding="utf-8"?>
+<!--
   Copyright 2021 The Android Open Source Project
 
   Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,10 +14,9 @@
   See the License for the specific language governing permissions and
   limitations under the License.
   -->
-
-<resources>
-    <string name="sort">Sort</string>
-    <string name="overview">Account overview</string>
-    <string name="account">Accounts</string>
-    <string name="bills">Bills</string>
-</resources>
\ No newline at end of file
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="androidx.compose.ui.test.manifest">
+    <application>
+        <activity android:name="androidx.activity.ComponentActivity" />
+    </application>
+</manifest>
diff --git a/compose/ui/ui-test/api/public_plus_experimental_1.0.0-beta02.txt b/compose/ui/ui-test/api/public_plus_experimental_1.0.0-beta02.txt
index e2b3a60..07ac8f8 100644
--- a/compose/ui/ui-test/api/public_plus_experimental_1.0.0-beta02.txt
+++ b/compose/ui/ui-test/api/public_plus_experimental_1.0.0-beta02.txt
@@ -157,9 +157,13 @@
     method public static void pinch-1c52nSY(androidx.compose.ui.test.GestureScope, long start0, long end0, long start1, long end1, optional long durationMillis);
     method public static void swipe-DPh1Mgw(androidx.compose.ui.test.GestureScope, long start, long end, optional long durationMillis);
     method public static void swipeDown(androidx.compose.ui.test.GestureScope);
+    method @androidx.compose.ui.test.ExperimentalTestApi public static void swipeDown(androidx.compose.ui.test.GestureScope, optional float startY, optional float endY, optional long durationMillis);
     method public static void swipeLeft(androidx.compose.ui.test.GestureScope);
+    method @androidx.compose.ui.test.ExperimentalTestApi public static void swipeLeft(androidx.compose.ui.test.GestureScope, optional float startX, optional float endX, optional long durationMillis);
     method public static void swipeRight(androidx.compose.ui.test.GestureScope);
+    method @androidx.compose.ui.test.ExperimentalTestApi public static void swipeRight(androidx.compose.ui.test.GestureScope, optional float startX, optional float endX, optional long durationMillis);
     method public static void swipeUp(androidx.compose.ui.test.GestureScope);
+    method @androidx.compose.ui.test.ExperimentalTestApi public static void swipeUp(androidx.compose.ui.test.GestureScope, optional float startY, optional float endY, optional long durationMillis);
     method public static void swipeWithVelocity-YsXUJPI(androidx.compose.ui.test.GestureScope, long start, long end, float endVelocity, optional long durationMillis);
     method public static void up(androidx.compose.ui.test.GestureScope, optional int pointerId);
   }
diff --git a/compose/ui/ui-test/api/public_plus_experimental_current.txt b/compose/ui/ui-test/api/public_plus_experimental_current.txt
index e2b3a60..07ac8f8 100644
--- a/compose/ui/ui-test/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-test/api/public_plus_experimental_current.txt
@@ -157,9 +157,13 @@
     method public static void pinch-1c52nSY(androidx.compose.ui.test.GestureScope, long start0, long end0, long start1, long end1, optional long durationMillis);
     method public static void swipe-DPh1Mgw(androidx.compose.ui.test.GestureScope, long start, long end, optional long durationMillis);
     method public static void swipeDown(androidx.compose.ui.test.GestureScope);
+    method @androidx.compose.ui.test.ExperimentalTestApi public static void swipeDown(androidx.compose.ui.test.GestureScope, optional float startY, optional float endY, optional long durationMillis);
     method public static void swipeLeft(androidx.compose.ui.test.GestureScope);
+    method @androidx.compose.ui.test.ExperimentalTestApi public static void swipeLeft(androidx.compose.ui.test.GestureScope, optional float startX, optional float endX, optional long durationMillis);
     method public static void swipeRight(androidx.compose.ui.test.GestureScope);
+    method @androidx.compose.ui.test.ExperimentalTestApi public static void swipeRight(androidx.compose.ui.test.GestureScope, optional float startX, optional float endX, optional long durationMillis);
     method public static void swipeUp(androidx.compose.ui.test.GestureScope);
+    method @androidx.compose.ui.test.ExperimentalTestApi public static void swipeUp(androidx.compose.ui.test.GestureScope, optional float startY, optional float endY, optional long durationMillis);
     method public static void swipeWithVelocity-YsXUJPI(androidx.compose.ui.test.GestureScope, long start, long end, float endVelocity, optional long durationMillis);
     method public static void up(androidx.compose.ui.test.GestureScope, optional int pointerId);
   }
diff --git a/compose/ui/ui-test/build.gradle b/compose/ui/ui-test/build.gradle
index 4fe73f6..4f251e1 100644
--- a/compose/ui/ui-test/build.gradle
+++ b/compose/ui/ui-test/build.gradle
@@ -17,7 +17,7 @@
 
 import androidx.build.AndroidXUiPlugin
 import androidx.build.LibraryGroups
-import androidx.build.Publish
+import androidx.build.LibraryType
 
 import static androidx.build.dependencies.DependenciesKt.*
 
@@ -118,7 +118,7 @@
 
 androidx {
     name = "Compose Testing"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.UI
     inceptionYear = "2019"
     description = "Compose testing library"
diff --git a/compose/ui/ui-test/lint-baseline.xml b/compose/ui/ui-test/lint-baseline.xml
index fb9c27d..75e9668 100644
--- a/compose/ui/ui-test/lint-baseline.xml
+++ b/compose/ui/ui-test/lint-baseline.xml
@@ -1,26 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
+<issues format="5" by="lint 4.2.0-beta04" client="gradle" variant="debug" version="4.2.0-beta04">
 
     <issue
         id="UnsafeNewApiCall"
@@ -29,7 +8,7 @@
         errorLine2="                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/androidMain/kotlin/androidx/compose/ui/test/android/WindowCapture.android.kt"
-            line="59"
+            line="64"
             column="40"/>
     </issue>
 
@@ -40,7 +19,7 @@
         errorLine2="              ~~~~~~~">
         <location
             file="src/androidMain/kotlin/androidx/compose/ui/test/android/WindowCapture.android.kt"
-            line="95"
+            line="100"
             column="15"/>
     </issue>
 
diff --git a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/ErrorMessagesTest.kt b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/ErrorMessagesTest.kt
index 2fbbab8..ba63e9f 100644
--- a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/ErrorMessagesTest.kt
+++ b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/ErrorMessagesTest.kt
@@ -30,7 +30,6 @@
 import androidx.compose.ui.semantics.SemanticsActions
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.util.expectErrorMessage
-import androidx.compose.ui.test.util.expectErrorMessageMatches
 import androidx.compose.ui.test.util.expectErrorMessageStartsWith
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -62,10 +61,10 @@
         }
 
         expectErrorMessage(
-            "" +
-                "Failed: assertExists.\n" +
-                "Reason: Expected exactly '1' node but could not find any node that satisfies: " +
-                "(TestTag = 'MyButton3')"
+            """
+                Failed: assertExists.
+                Reason: Expected exactly '1' node but could not find any node that satisfies: (TestTag = 'MyButton3')
+            """.trimIndent()
         ) {
             rule.onNodeWithTag("MyButton3")
                 .assertExists()
@@ -79,10 +78,11 @@
         }
 
         expectErrorMessage(
-            "" +
-                "Failed to perform a gesture.\n" +
-                "Reason: Expected exactly '1' node but could not find any node that satisfies: " +
-                "(TestTag = 'MyButton3')"
+            """
+                Failed to perform a gesture.
+                Reason: Expected exactly '1' node but could not find any node that satisfies: (TestTag = 'MyButton3')
+            """.trimIndent()
+
         ) {
             rule.onNodeWithTag("MyButton3")
                 .performClick()
@@ -96,10 +96,10 @@
         }
 
         expectErrorMessage(
-            "" +
-                "Failed to perform a gesture.\n" +
-                "Reason: Expected exactly '1' node but could not find any node that satisfies: " +
-                "((TestTag = 'MyButton3') && (OnClick is defined))"
+            """
+                Failed to perform a gesture.
+                Reason: Expected exactly '1' node but could not find any node that satisfies: ((TestTag = 'MyButton3') && (OnClick is defined))
+            """.trimIndent()
         ) {
             rule.onNode(hasTestTag("MyButton3") and hasClickAction())
                 .performClick()
@@ -113,12 +113,12 @@
         }
 
         expectErrorMessageStartsWith(
-            "" +
-                "Failed to perform a gesture.\n" +
-                "Reason: Expected exactly '1' node but found '2' nodes that satisfy: " +
-                "(Text = 'Toggle' (ignoreCase: false))\n" +
-                "Nodes found:\n" +
-                "1) Node #X at (l=X, t=X, r=X, b=X)px, Tag: 'MyButton'"
+            """
+                Failed to perform a gesture.
+                Reason: Expected exactly '1' node but found '2' nodes that satisfy: (Text = 'Toggle' (ignoreCase: false))
+                Nodes found:
+                1) Node #X at (l=X, t=X, r=X, b=X)px, Tag: 'MyButton'
+            """.trimIndent()
         ) {
             rule.onNodeWithText("Toggle")
                 .performClick()
@@ -132,10 +132,10 @@
         }
 
         expectErrorMessageStartsWith(
-            "" +
-                "Failed to perform OnClick action.\n" +
-                "Reason: Expected exactly '1' node but could not find any node that satisfies: " +
-                "(TestTag = 'MyButton3')"
+            """
+                Failed to perform OnClick action.
+                Reason: Expected exactly '1' node but could not find any node that satisfies: (TestTag = 'MyButton3')
+            """.trimIndent()
         ) {
             rule.onNodeWithTag("MyButton3")
                 .performSemanticsAction(SemanticsActions.OnClick)
@@ -149,12 +149,12 @@
         }
 
         expectErrorMessageStartsWith(
-            "" +
-                "Failed: assertDoesNotExist.\n" +
-                "Reason: Did not expect any node but found '1' node that satisfies: " +
-                "(TestTag = 'MyButton')\n" +
-                "Node found:\n" +
-                "Node #X at (l=X, t=X, r=X, b=X)px, Tag: 'MyButton'"
+            """
+                Failed: assertDoesNotExist.
+                Reason: Did not expect any node but found '1' node that satisfies: (TestTag = 'MyButton')
+                Node found:
+                Node #X at (l=X, t=X, r=X, b=X)px, Tag: 'MyButton'
+            """.trimIndent()
         ) {
             rule.onNodeWithTag("MyButton")
                 .assertDoesNotExist()
@@ -168,12 +168,12 @@
         }
 
         expectErrorMessageStartsWith(
-            "" +
-                "Failed to assert count of nodes.\n" +
-                "Reason: Expected '3' nodes but found '2' nodes that satisfy: " +
-                "(Text = 'Toggle' (ignoreCase: false))\n" +
-                "Nodes found:\n" +
-                "1) Node #X at (l=X, t=X, r=X, b=X)px"
+            """
+                Failed to assert count of nodes.
+                Reason: Expected '3' nodes but found '2' nodes that satisfy: (Text = 'Toggle' (ignoreCase: false))
+                Nodes found:
+                1) Node #X at (l=X, t=X, r=X, b=X)px
+            """.trimIndent()
         ) {
             rule.onAllNodesWithText("Toggle")
                 .assertCountEquals(3)
@@ -187,10 +187,10 @@
         }
 
         expectErrorMessage(
-            "" +
-                "Failed to assert count of nodes.\n" +
-                "Reason: Expected '3' nodes but could not find any node that satisfies: " +
-                "(Text = 'Toggle2' (ignoreCase: false))"
+            """
+                Failed to assert count of nodes.
+                Reason: Expected '3' nodes but could not find any node that satisfies: (Text = 'Toggle2' (ignoreCase: false))
+            """.trimIndent()
         ) {
             rule.onAllNodesWithText("Toggle2")
                 .assertCountEquals(3)
@@ -209,15 +209,16 @@
         rule.onNodeWithTag("MyButton")
             .performClick()
 
-        expectErrorMessageMatches(
-            "" +
-                "Failed to perform a gesture.\n" +
-                "The node is no longer in the tree, last known semantics:\n" +
-                "Node #X at \\(l=X, t=X, r=X, b=X\\)px\n" +
-                "Text = 'Hello'\n" +
-                "GetTextLayoutResult = 'AccessibilityAction\\(label=null, action=.*\\)'\n" +
-                "Has 1 sibling\n" +
-                "Original selector: Text = 'Hello' \\(ignoreCase: false\\)"
+        expectErrorMessage(
+            """
+                Failed to perform a gesture.
+                The node is no longer in the tree, last known semantics:
+                Node #X at (l=X, t=X, r=X, b=X)px
+                Text = 'Hello'
+                Actions = [GetTextLayoutResult]
+                Has 1 sibling
+                Original selector: Text = 'Hello' (ignoreCase: false)
+            """.trimIndent()
         ) {
             node.performClick()
         }
@@ -236,15 +237,16 @@
         rule.onNodeWithTag("MyButton")
             .performClick()
 
-        expectErrorMessageMatches(
-            "" +
-                "Failed: assertExists.\n" +
-                "The node is no longer in the tree, last known semantics:\n" +
-                "Node #X at \\(l=X, t=X, r=X, b=X\\)px\n" +
-                "Text = 'Hello'\n" +
-                "GetTextLayoutResult = 'AccessibilityAction\\(label=null, action=.*\\)'\n" +
-                "Has 1 sibling\n" +
-                "Original selector: Text = 'Hello' \\(ignoreCase: false\\)"
+        expectErrorMessage(
+            """
+                Failed: assertExists.
+                The node is no longer in the tree, last known semantics:
+                Node #X at (l=X, t=X, r=X, b=X)px
+                Text = 'Hello'
+                Actions = [GetTextLayoutResult]
+                Has 1 sibling
+                Original selector: Text = 'Hello' (ignoreCase: false)
+            """.trimIndent()
         ) {
             node.assertExists()
         }
@@ -263,15 +265,16 @@
         rule.onNodeWithTag("MyButton")
             .performClick()
 
-        expectErrorMessageMatches(
-            "" +
-                "Failed to assert the following: \\(OnClick is defined\\)\n" +
-                "The node is no longer in the tree, last known semantics:\n" +
-                "Node #X at \\(l=X, t=X, r=X, b=X\\)px\n" +
-                "Text = 'Hello'\n" +
-                "GetTextLayoutResult = 'AccessibilityAction\\(label=null, action=.*\\)'\n" +
-                "Has 1 sibling\n" +
-                "Original selector: Text = 'Hello' \\(ignoreCase: false\\)"
+        expectErrorMessage(
+            """
+                Failed to assert the following: (OnClick is defined)
+                The node is no longer in the tree, last known semantics:
+                Node #X at (l=X, t=X, r=X, b=X)px
+                Text = 'Hello'
+                Actions = [GetTextLayoutResult]
+                Has 1 sibling
+                Original selector: Text = 'Hello' (ignoreCase: false)
+            """.trimIndent()
         ) {
             node.assertHasClickAction()
         }
diff --git a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/PrintToStringTest.kt b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/PrintToStringTest.kt
index bdf172f..3d7c59b 100644
--- a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/PrintToStringTest.kt
+++ b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/PrintToStringTest.kt
@@ -67,12 +67,14 @@
         val result = rule.onNodeWithText("Hello")
             .printToString(maxDepth = 0)
 
-        assertThat(obfuscateNodesInfo(result)).matches(
-            "" +
-                "Node #X at \\(l=X, t=X, r=X, b=X\\)px\n" +
-                "Text = 'Hello'\n" +
-                "GetTextLayoutResult = 'AccessibilityAction\\(label=null, action=.*\\)'\n" +
-                "Has 1 sibling"
+        assertThat(obfuscateNodesInfo(result)).isEqualTo(
+            """
+                Printing with useUnmergedTree = 'false'
+                Node #X at (l=X, t=X, r=X, b=X)px
+                Text = 'Hello'
+                Actions = [GetTextLayoutResult]
+                Has 1 sibling
+            """.trimIndent()
         )
     }
 
@@ -86,16 +88,18 @@
             .onChildren()
             .printToString()
 
-        assertThat(obfuscateNodesInfo(result)).matches(
-            "" +
-                "1\\) Node #X at \\(l=X, t=X, r=X, b=X\\)px\n" +
-                "Text = 'Hello'\n" +
-                "GetTextLayoutResult = 'AccessibilityAction\\(label=null, action=.*\\)'\n" +
-                "Has 1 sibling\n" +
-                "2\\) Node #X at \\(l=X, t=X, r=X, b=X\\)px\n" +
-                "Text = 'World'\n" +
-                "GetTextLayoutResult = 'AccessibilityAction\\(label=null, action=.*\\)'\n" +
-                "Has 1 sibling"
+        assertThat(obfuscateNodesInfo(result)).isEqualTo(
+            """
+                Printing with useUnmergedTree = 'false'
+                1) Node #X at (l=X, t=X, r=X, b=X)px
+                Text = 'Hello'
+                Actions = [GetTextLayoutResult]
+                Has 1 sibling
+                2) Node #X at (l=X, t=X, r=X, b=X)px
+                Text = 'World'
+                Actions = [GetTextLayoutResult]
+                Has 1 sibling
+            """.trimIndent()
         )
     }
 
@@ -115,23 +119,23 @@
         val result = rule.onRoot()
             .printToString()
 
-        assertThat(obfuscateNodesInfo(result)).matches(
-            "" +
-                "Node #X at \\(l=X, t=X, r=X, b=X\\)px\n" +
-                " ..*Node #X at \\(l=X, t=X, r=X, b=X\\)px, Tag: 'column'\n" +
-                "   Disabled = 'kotlin.Unit'\n" +
-                "    .-Node #X at \\(l=X, t=X, r=X, b=X\\)px, Tag: 'box'\n" +
-                "    . Disabled = 'kotlin.Unit'\n" +
-                "    .  .-Node #X at \\(l=X, t=X, r=X, b=X\\)px\n" +
-                "    .    Role = 'Button'\n" +
-                "    .    OnClick = 'AccessibilityAction\\(label=null, action=.*\\)'\n" +
-                "    .    Text = 'Button'\n" +
-                "    .    GetTextLayoutResult = 'AccessibilityAction\\(label=null, " +
-                "action=.*\\)'\n" +
-                "    .    MergeDescendants = 'true'\n" +
-                "    .-Node #X at \\(l=X, t=X, r=X, b=X\\)px\n" +
-                "      Text = 'Hello'\n" +
-                "      GetTextLayoutResult = 'AccessibilityAction\\(label=null, action=.*\\).*'"
+        assertThat(obfuscateNodesInfo(result)).isEqualTo(
+            """
+                Printing with useUnmergedTree = 'false'
+                Node #X at (l=X, t=X, r=X, b=X)px
+                 |-Node #X at (l=X, t=X, r=X, b=X)px, Tag: 'column'
+                   [Disabled]
+                    |-Node #X at (l=X, t=X, r=X, b=X)px, Tag: 'box'
+                    | [Disabled]
+                    |  |-Node #X at (l=X, t=X, r=X, b=X)px
+                    |    Role = 'Button'
+                    |    Text = 'Button'
+                    |    Actions = [OnClick, GetTextLayoutResult]
+                    |    MergeDescendants = 'true'
+                    |-Node #X at (l=X, t=X, r=X, b=X)px
+                      Text = 'Hello'
+                      Actions = [GetTextLayoutResult]
+            """.trimIndent()
         )
     }
 
@@ -155,13 +159,15 @@
             .printToString(maxDepth = 1)
 
         assertThat(obfuscateNodesInfo(result)).isEqualTo(
-            "" +
-                "1) Node #X at (l=X, t=X, r=X, b=X)px, Tag: 'tag1'\n" +
-                " |-Node #X at (l=X, t=X, r=X, b=X)px, Tag: 'tag11'\n" +
-                "   Has 1 child\n" +
-                "2) Node #X at (l=X, t=X, r=X, b=X)px, Tag: 'tag2'\n" +
-                " |-Node #X at (l=X, t=X, r=X, b=X)px, Tag: 'tag22'\n" +
-                "   Has 1 child"
+            """
+                Printing with useUnmergedTree = 'false'
+                1) Node #X at (l=X, t=X, r=X, b=X)px, Tag: 'tag1'
+                 |-Node #X at (l=X, t=X, r=X, b=X)px, Tag: 'tag11'
+                   Has 1 child
+                2) Node #X at (l=X, t=X, r=X, b=X)px, Tag: 'tag2'
+                 |-Node #X at (l=X, t=X, r=X, b=X)px, Tag: 'tag22'
+                   Has 1 child
+            """.trimIndent()
         )
     }
 
diff --git a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/assertions/BoundsAssertionsTest.kt b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/assertions/BoundsAssertionsTest.kt
index 87afff9..e408a8b 100644
--- a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/assertions/BoundsAssertionsTest.kt
+++ b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/assertions/BoundsAssertionsTest.kt
@@ -160,8 +160,8 @@
                 // so it is clipped to a size of 50 x 90
                 Box(
                     modifier = Modifier
-                        .testTag(tag)
                         .offset((-30).dp, (-10).dp)
+                        .testTag(tag)
                         .requiredSize(80.dp, 100.dp)
                         .background(color = Color.Black)
                 )
diff --git a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/gesturescope/SendSwipeTest.kt b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/gesturescope/SendSwipeTest.kt
index c44bddb..e01eadf 100644
--- a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/gesturescope/SendSwipeTest.kt
+++ b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/gesturescope/SendSwipeTest.kt
@@ -25,6 +25,7 @@
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.testutils.expectError
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
@@ -32,8 +33,11 @@
 import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.platform.ViewConfiguration
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.bottomCenter
 import androidx.compose.ui.test.bottomRight
+import androidx.compose.ui.test.centerX
+import androidx.compose.ui.test.centerY
 import androidx.compose.ui.test.down
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.moveTo
@@ -135,6 +139,106 @@
     }
 
     @Test
+    fun swipeUp_withParameters() {
+        rule.setContent { Ui(Alignment.TopStart) }
+        @OptIn(ExperimentalTestApi::class)
+        rule.onNodeWithTag(tag).performGesture { swipeUp(endY = centerY) }
+        rule.runOnIdle {
+            recorder.run {
+                assertTimestampsAreIncreasing()
+                assertOnlyLastEventIsUp()
+                assertSwipeIsUp()
+            }
+        }
+    }
+
+    @Test
+    fun swipeDown_withParameters() {
+        rule.setContent { Ui(Alignment.TopEnd) }
+        @OptIn(ExperimentalTestApi::class)
+        rule.onNodeWithTag(tag).performGesture { swipeDown(endY = centerY) }
+        rule.runOnIdle {
+            recorder.run {
+                assertTimestampsAreIncreasing()
+                assertOnlyLastEventIsUp()
+                assertSwipeIsDown()
+            }
+        }
+    }
+
+    @Test
+    fun swipeLeft_withParameters() {
+        rule.setContent { Ui(Alignment.BottomEnd) }
+        @OptIn(ExperimentalTestApi::class)
+        rule.onNodeWithTag(tag).performGesture { swipeLeft(endX = centerX) }
+        rule.runOnIdle {
+            recorder.run {
+                assertTimestampsAreIncreasing()
+                assertOnlyLastEventIsUp()
+                assertSwipeIsLeft()
+            }
+        }
+    }
+
+    @Test
+    fun swipeRight_withParameters() {
+        rule.setContent { Ui(Alignment.BottomStart) }
+        @OptIn(ExperimentalTestApi::class)
+        rule.onNodeWithTag(tag).performGesture { swipeRight(endX = centerX) }
+        rule.runOnIdle {
+            recorder.run {
+                assertTimestampsAreIncreasing()
+                assertOnlyLastEventIsUp()
+                assertSwipeIsRight()
+            }
+        }
+    }
+
+    @Test
+    fun swipeUp_wrongParameters() {
+        rule.setContent { Ui(Alignment.TopStart) }
+        expectError<IllegalArgumentException>(
+            expectedMessage = "startY=0.0 needs to be greater than or equal to endY=1.0"
+        ) {
+            @OptIn(ExperimentalTestApi::class)
+            rule.onNodeWithTag(tag).performGesture { swipeUp(startY = 0f, endY = 1f) }
+        }
+    }
+
+    @Test
+    fun swipeDown_wrongParameters() {
+        rule.setContent { Ui(Alignment.TopEnd) }
+        expectError<IllegalArgumentException>(
+            expectedMessage = "startY=1.0 needs to be less than or equal to endY=0.0"
+        ) {
+            @OptIn(ExperimentalTestApi::class)
+            rule.onNodeWithTag(tag).performGesture { swipeDown(startY = 1f, endY = 0f) }
+        }
+    }
+
+    @Test
+    fun swipeLeft_wrongParameters() {
+        rule.setContent { Ui(Alignment.BottomEnd) }
+        expectError<IllegalArgumentException>(
+            expectedMessage = "startX=0.0 needs to be greater than or equal to endX=1.0"
+        ) {
+            @OptIn(ExperimentalTestApi::class)
+            rule.onNodeWithTag(tag).performGesture { swipeLeft(startX = 0f, endX = 1f) }
+        }
+    }
+
+    @Test
+    fun swipeRight_wrongParameters() {
+        rule.setContent { Ui(Alignment.BottomStart) }
+        expectError<IllegalArgumentException>(
+            expectedMessage = "startX=1.0 needs to be less than or equal to endX=0.0"
+        ) {
+            @OptIn(ExperimentalTestApi::class)
+            rule.onNodeWithTag(tag).performGesture { swipeRight(startX = 1f, endX = 0f) }
+        }
+    }
+
+    @Test
     fun swipeShort() {
         rule.setContent { Ui(Alignment.Center) }
         rule.onNodeWithTag(tag).performGesture { swipe(topLeft, bottomRight, 1) }
diff --git a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/util/Output.kt b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/util/Output.kt
index 83d6af9..a67ef92 100644
--- a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/util/Output.kt
+++ b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/util/Output.kt
@@ -40,18 +40,6 @@
     throw AssertionError("No AssertionError thrown!")
 }
 
-internal fun expectErrorMessageMatches(expectedErrorMessage: String, block: () -> Unit) {
-    try {
-        block()
-    } catch (e: AssertionError) {
-        val received = obfuscateNodesInfo(e.localizedMessage!!)
-        Truth.assertThat(received).matches(expectedErrorMessage.trim())
-        return
-    }
-
-    throw AssertionError("No AssertionError thrown!")
-}
-
 internal fun expectErrorMessageStartsWith(expectedErrorMessage: String, block: () -> Unit) {
     try {
         block()
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/GestureScope.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/GestureScope.kt
index 6f574ea..d180bc8 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/GestureScope.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/GestureScope.kt
@@ -465,58 +465,150 @@
 }
 
 /**
- * Performs a swipe up gesture on the associated node. The gesture starts slightly above the
- * bottom of the node and ends at the top.
+ * Performs a swipe up gesture along the [centerX] of the associated node. The gesture starts
+ * slightly above the [bottom] of the node and ends at the [top].
  */
 fun GestureScope.swipeUp() {
-    val x = center.x
-    val y0 = (visibleSize.height * (1 - edgeFuzzFactor)).roundToInt().toFloat()
-    val y1 = 0.0f
-    val start = Offset(x, y0)
-    val end = Offset(x, y1)
+    val start = Offset(centerX, bottomFuzzed)
+    val end = Offset(centerX, top)
     swipe(start, end, 200)
 }
 
 /**
- * Performs a swipe down gesture on the associated node. The gesture starts slightly below the
- * top of the node and ends at the bottom.
+ * Performs a swipe up gesture along the [centerX] of the associated node, from [startY] till
+ * [endY], taking [durationMillis] milliseconds.
+ *
+ * @param startY The y-coordinate of the start of the swipe. Must be greater than or equal to the
+ * [endY]. By default slightly above the [bottom] of the node.
+ * @param endY The y-coordinate of the end of the swipe. Must be less than or equal to the
+ * [startY]. By default the [top] of the node.
+ * @param durationMillis The duration of the swipe. By default 200 milliseconds.
+ */
+@ExperimentalTestApi
+fun GestureScope.swipeUp(
+    startY: Float = bottomFuzzed,
+    endY: Float = top,
+    durationMillis: Long = 200
+) {
+    require(startY >= endY) {
+        "startY=$startY needs to be greater than or equal to endY=$endY"
+    }
+    val start = Offset(centerX, startY)
+    val end = Offset(centerX, endY)
+    swipe(start, end, durationMillis)
+}
+
+/**
+ * Performs a swipe down gesture along the [centerX] of the associated node. The gesture starts
+ * slightly below the [top] of the node and ends at the [bottom].
  */
 fun GestureScope.swipeDown() {
-    val x = center.x
-    val y0 = (visibleSize.height * edgeFuzzFactor).roundToInt().toFloat()
-    val y1 = visibleSize.height.toFloat()
-    val start = Offset(x, y0)
-    val end = Offset(x, y1)
+    val start = Offset(centerX, topFuzzed)
+    val end = Offset(centerX, bottom)
     swipe(start, end, 200)
 }
 
 /**
- * Performs a swipe left gesture on the associated node. The gesture starts slightly left of
- * the right side of the node and ends at the left side.
+ * Performs a swipe down gesture along the [centerX] of the associated node, from [startY] till
+ * [endY], taking [durationMillis] milliseconds.
+ *
+ * @param startY The y-coordinate of the start of the swipe. Must be less than or equal to the
+ * [endY]. By default slightly below the [top] of the node.
+ * @param endY The y-coordinate of the end of the swipe. Must be greater than or equal to the
+ * [startY]. By default the [bottom] of the node.
+ * @param durationMillis The duration of the swipe. By default 200 milliseconds.
+ */
+@ExperimentalTestApi
+fun GestureScope.swipeDown(
+    startY: Float = topFuzzed,
+    endY: Float = bottom,
+    durationMillis: Long = 200
+) {
+    require(startY <= endY) {
+        "startY=$startY needs to be less than or equal to endY=$endY"
+    }
+    val start = Offset(centerX, startY)
+    val end = Offset(centerX, endY)
+    swipe(start, end, durationMillis)
+}
+
+/**
+ * Performs a swipe left gesture along the [centerY] of the associated node. The gesture starts
+ * slightly left of the [right] side of the node and ends at the [left] side.
  */
 fun GestureScope.swipeLeft() {
-    val x0 = (visibleSize.width * (1 - edgeFuzzFactor)).roundToInt().toFloat()
-    val x1 = 0.0f
-    val y = center.y
-    val start = Offset(x0, y)
-    val end = Offset(x1, y)
+    val start = Offset(rightFuzzed, centerY)
+    val end = Offset(left, centerY)
     swipe(start, end, 200)
 }
 
 /**
- * Performs a swipe right gesture on the associated node. The gesture starts slightly right of
- * the left side of the node and ends at the right side.
+ * Performs a swipe left gesture along the [centerY] of the associated node, from [startX] till
+ * [endX], taking [durationMillis] milliseconds.
+ *
+ * @param startX The x-coordinate of the start of the swipe. Must be greater than or equal to the
+ * [endX]. By default slightly left of the [right] of the node.
+ * @param endX The x-coordinate of the end of the swipe. Must be less than or equal to the
+ * [startX]. By default the [left] of the node.
+ * @param durationMillis The duration of the swipe. By default 200 milliseconds.
+ */
+@ExperimentalTestApi
+fun GestureScope.swipeLeft(
+    startX: Float = rightFuzzed,
+    endX: Float = left,
+    durationMillis: Long = 200
+) {
+    require(startX >= endX) {
+        "startX=$startX needs to be greater than or equal to endX=$endX"
+    }
+    val start = Offset(startX, centerY)
+    val end = Offset(endX, centerY)
+    swipe(start, end, durationMillis)
+}
+
+/**
+ * Performs a swipe right gesture along the [centerY] of the associated node. The gesture starts
+ * slightly right of the [left] side of the node and ends at the [right] side.
  */
 fun GestureScope.swipeRight() {
-    val x0 = (visibleSize.width * edgeFuzzFactor).roundToInt().toFloat()
-    val x1 = visibleSize.width.toFloat()
-    val y = center.y
-    val start = Offset(x0, y)
-    val end = Offset(x1, y)
+    val start = Offset(leftFuzzed, centerY)
+    val end = Offset(right, centerY)
     swipe(start, end, 200)
 }
 
 /**
+ * Performs a swipe right gesture along the [centerY] of the associated node, from [startX] till
+ * [endX], taking [durationMillis] milliseconds.
+ *
+ * @param startX The x-coordinate of the start of the swipe. Must be less than or equal to the
+ * [endX]. By default slightly right of the [left] of the node.
+ * @param endX The x-coordinate of the end of the swipe. Must be greater than or equal to the
+ * [startX]. By default the [right] of the node.
+ * @param durationMillis The duration of the swipe. By default 200 milliseconds.
+ */
+@ExperimentalTestApi
+fun GestureScope.swipeRight(
+    startX: Float = leftFuzzed,
+    endX: Float = right,
+    durationMillis: Long = 200
+) {
+    require(startX <= endX) {
+        "startX=$startX needs to be less than or equal to endX=$endX"
+    }
+    val start = Offset(startX, centerY)
+    val end = Offset(endX, centerY)
+    swipe(start, end, durationMillis)
+}
+
+private val Int.startFuzzed: Float get() = (this * edgeFuzzFactor).roundToInt().toFloat()
+private val Int.endFuzzed: Float get() = (this * (1 - edgeFuzzFactor)).roundToInt().toFloat()
+
+private val GestureScope.leftFuzzed: Float get() = width.startFuzzed
+private val GestureScope.topFuzzed: Float get() = height.startFuzzed
+private val GestureScope.rightFuzzed: Float get() = width.endFuzzed
+private val GestureScope.bottomFuzzed: Float get() = height.endFuzzed
+
+/**
  * Generate a function of the form `f(t) = a*(t-T)^2 + b*(t-T) + c` that satisfies
  * `f(0) = [start]`, `f([duration]) = [end]`, `T = [duration]` and `b = [velocity]`.
  *
diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Output.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Output.kt
index f2780d1..5ac3060 100644
--- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Output.kt
+++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/Output.kt
@@ -17,6 +17,7 @@
 package androidx.compose.ui.test
 
 import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.semantics.AccessibilityAction
 import androidx.compose.ui.semantics.SemanticsConfiguration
 import androidx.compose.ui.semantics.SemanticsNode
 import androidx.compose.ui.semantics.SemanticsProperties
@@ -42,7 +43,8 @@
     maxDepth: Int = Int.MAX_VALUE
 ): String {
     val result = fetchSemanticsNode()
-    return result.printToString(maxDepth)
+    return "Printing with useUnmergedTree = '$useUnmergedTree'\n" +
+        result.printToString(maxDepth)
 }
 
 /**
@@ -84,11 +86,12 @@
     maxDepth: Int = 0
 ): String {
     val nodes = fetchSemanticsNodes()
-    return if (nodes.isEmpty()) {
-        "There were 0 nodes found!"
-    } else {
-        nodes.printToString(maxDepth)
-    }
+    return "Printing with useUnmergedTree = '$useUnmergedTree'\n" +
+        if (nodes.isEmpty()) {
+            "There were 0 nodes found!"
+        } else {
+            nodes.printToString(maxDepth)
+        }
 }
 
 /**
@@ -218,11 +221,25 @@
 }
 
 private fun StringBuilder.appendConfigInfo(config: SemanticsConfiguration, indent: String = "") {
+    val actions = mutableListOf<String>()
+    val units = mutableListOf<String>()
     for ((key, value) in config) {
         if (key == SemanticsProperties.TestTag) {
             continue
         }
 
+        if (value is AccessibilityAction<*>) {
+            // Avoids printing stuff like "action = 'AccessibilityAction\(label=null, action=.*\)'"
+            actions.add(key.name)
+            continue
+        }
+
+        if (value is Unit) {
+            // Avoids printing stuff like "Disabled = 'kotlin.Unit'"
+            units.add(key.name)
+            continue
+        }
+
         appendLine()
         append(indent)
         append(key.name)
@@ -244,6 +261,22 @@
         append("'")
     }
 
+    if (units.isNotEmpty()) {
+        appendLine()
+        append(indent)
+        append("[")
+        append(units.joinToString(separator = ", "))
+        append("]")
+    }
+
+    if (actions.isNotEmpty()) {
+        appendLine()
+        append(indent)
+        append("Actions = [")
+        append(actions.joinToString(separator = ", "))
+        append("]")
+    }
+
     if (config.isMergingSemanticsOfDescendants) {
         appendLine()
         append(indent)
diff --git a/compose/ui/ui-text/api/1.0.0-beta02.txt b/compose/ui/ui-text/api/1.0.0-beta02.txt
index 3b624da..255fb72 100644
--- a/compose/ui/ui-text/api/1.0.0-beta02.txt
+++ b/compose/ui/ui-text/api/1.0.0-beta02.txt
@@ -292,6 +292,9 @@
     method public static String toUpperCase(String, androidx.compose.ui.text.intl.LocaleList localeList);
   }
 
+  public final class TempListUtilsKt {
+  }
+
   public final class TextLayoutInput {
     method public androidx.compose.ui.text.TextLayoutInput copy-ih31NyA(optional androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.text.TextStyle style, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean softWrap, optional androidx.compose.ui.text.style.TextOverflow overflow, optional androidx.compose.ui.unit.Density density, optional androidx.compose.ui.unit.LayoutDirection layoutDirection, optional androidx.compose.ui.text.font.Font.ResourceLoader resourceLoader, optional long constraints);
     method public operator boolean equals(Object? other);
@@ -479,6 +482,9 @@
   public final class LayoutIntrinsicsKt {
   }
 
+  public final class TempListUtilsKt {
+  }
+
   public final class TextLayoutKt {
   }
 
diff --git a/compose/ui/ui-text/api/current.txt b/compose/ui/ui-text/api/current.txt
index 3b624da..255fb72 100644
--- a/compose/ui/ui-text/api/current.txt
+++ b/compose/ui/ui-text/api/current.txt
@@ -292,6 +292,9 @@
     method public static String toUpperCase(String, androidx.compose.ui.text.intl.LocaleList localeList);
   }
 
+  public final class TempListUtilsKt {
+  }
+
   public final class TextLayoutInput {
     method public androidx.compose.ui.text.TextLayoutInput copy-ih31NyA(optional androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.text.TextStyle style, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean softWrap, optional androidx.compose.ui.text.style.TextOverflow overflow, optional androidx.compose.ui.unit.Density density, optional androidx.compose.ui.unit.LayoutDirection layoutDirection, optional androidx.compose.ui.text.font.Font.ResourceLoader resourceLoader, optional long constraints);
     method public operator boolean equals(Object? other);
@@ -479,6 +482,9 @@
   public final class LayoutIntrinsicsKt {
   }
 
+  public final class TempListUtilsKt {
+  }
+
   public final class TextLayoutKt {
   }
 
diff --git a/compose/ui/ui-text/api/public_plus_experimental_1.0.0-beta02.txt b/compose/ui/ui-text/api/public_plus_experimental_1.0.0-beta02.txt
index 00cd8e6..4656604 100644
--- a/compose/ui/ui-text/api/public_plus_experimental_1.0.0-beta02.txt
+++ b/compose/ui/ui-text/api/public_plus_experimental_1.0.0-beta02.txt
@@ -295,6 +295,9 @@
     method public static String toUpperCase(String, androidx.compose.ui.text.intl.LocaleList localeList);
   }
 
+  public final class TempListUtilsKt {
+  }
+
   public final class TextLayoutInput {
     method public androidx.compose.ui.text.TextLayoutInput copy-ih31NyA(optional androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.text.TextStyle style, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean softWrap, optional androidx.compose.ui.text.style.TextOverflow overflow, optional androidx.compose.ui.unit.Density density, optional androidx.compose.ui.unit.LayoutDirection layoutDirection, optional androidx.compose.ui.text.font.Font.ResourceLoader resourceLoader, optional long constraints);
     method public operator boolean equals(Object? other);
@@ -485,6 +488,9 @@
   public final class LayoutIntrinsicsKt {
   }
 
+  public final class TempListUtilsKt {
+  }
+
   public final class TextLayoutKt {
   }
 
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 00cd8e6..4656604 100644
--- a/compose/ui/ui-text/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-text/api/public_plus_experimental_current.txt
@@ -295,6 +295,9 @@
     method public static String toUpperCase(String, androidx.compose.ui.text.intl.LocaleList localeList);
   }
 
+  public final class TempListUtilsKt {
+  }
+
   public final class TextLayoutInput {
     method public androidx.compose.ui.text.TextLayoutInput copy-ih31NyA(optional androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.text.TextStyle style, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean softWrap, optional androidx.compose.ui.text.style.TextOverflow overflow, optional androidx.compose.ui.unit.Density density, optional androidx.compose.ui.unit.LayoutDirection layoutDirection, optional androidx.compose.ui.text.font.Font.ResourceLoader resourceLoader, optional long constraints);
     method public operator boolean equals(Object? other);
@@ -485,6 +488,9 @@
   public final class LayoutIntrinsicsKt {
   }
 
+  public final class TempListUtilsKt {
+  }
+
   public final class TextLayoutKt {
   }
 
diff --git a/compose/ui/ui-text/api/restricted_1.0.0-beta02.txt b/compose/ui/ui-text/api/restricted_1.0.0-beta02.txt
index 3b624da..255fb72 100644
--- a/compose/ui/ui-text/api/restricted_1.0.0-beta02.txt
+++ b/compose/ui/ui-text/api/restricted_1.0.0-beta02.txt
@@ -292,6 +292,9 @@
     method public static String toUpperCase(String, androidx.compose.ui.text.intl.LocaleList localeList);
   }
 
+  public final class TempListUtilsKt {
+  }
+
   public final class TextLayoutInput {
     method public androidx.compose.ui.text.TextLayoutInput copy-ih31NyA(optional androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.text.TextStyle style, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean softWrap, optional androidx.compose.ui.text.style.TextOverflow overflow, optional androidx.compose.ui.unit.Density density, optional androidx.compose.ui.unit.LayoutDirection layoutDirection, optional androidx.compose.ui.text.font.Font.ResourceLoader resourceLoader, optional long constraints);
     method public operator boolean equals(Object? other);
@@ -479,6 +482,9 @@
   public final class LayoutIntrinsicsKt {
   }
 
+  public final class TempListUtilsKt {
+  }
+
   public final class TextLayoutKt {
   }
 
diff --git a/compose/ui/ui-text/api/restricted_current.txt b/compose/ui/ui-text/api/restricted_current.txt
index 3b624da..255fb72 100644
--- a/compose/ui/ui-text/api/restricted_current.txt
+++ b/compose/ui/ui-text/api/restricted_current.txt
@@ -292,6 +292,9 @@
     method public static String toUpperCase(String, androidx.compose.ui.text.intl.LocaleList localeList);
   }
 
+  public final class TempListUtilsKt {
+  }
+
   public final class TextLayoutInput {
     method public androidx.compose.ui.text.TextLayoutInput copy-ih31NyA(optional androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.text.TextStyle style, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional int maxLines, optional boolean softWrap, optional androidx.compose.ui.text.style.TextOverflow overflow, optional androidx.compose.ui.unit.Density density, optional androidx.compose.ui.unit.LayoutDirection layoutDirection, optional androidx.compose.ui.text.font.Font.ResourceLoader resourceLoader, optional long constraints);
     method public operator boolean equals(Object? other);
@@ -479,6 +482,9 @@
   public final class LayoutIntrinsicsKt {
   }
 
+  public final class TempListUtilsKt {
+  }
+
   public final class TextLayoutKt {
   }
 
diff --git a/compose/ui/ui-text/benchmark/lint-baseline.xml b/compose/ui/ui-text/benchmark/lint-baseline.xml
index b7f4e1a..5ef00a8 100644
--- a/compose/ui/ui-text/benchmark/lint-baseline.xml
+++ b/compose/ui/ui-text/benchmark/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
+<issues format="5" by="lint 4.2.0-beta04" client="gradle" variant="debug" version="4.2.0-beta04">
 
     <issue
         id="BanUncheckedReflection"
@@ -8,7 +8,7 @@
         errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/main/java/androidx/compose/ui/text/benchmark/TextBenchmarkTestRule.kt"
-            line="82"
+            line="81"
             column="13"/>
     </issue>
 
diff --git a/compose/ui/ui-text/build.gradle b/compose/ui/ui-text/build.gradle
index 41b0a4c..07f2cfb 100644
--- a/compose/ui/ui-text/build.gradle
+++ b/compose/ui/ui-text/build.gradle
@@ -17,7 +17,7 @@
 
 import androidx.build.AndroidXUiPlugin
 import androidx.build.LibraryGroups
-import androidx.build.Publish
+import androidx.build.LibraryType
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 import static androidx.build.dependencies.DependenciesKt.*
@@ -162,7 +162,7 @@
 
 androidx {
     name = "Compose UI Text"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.UI
     inceptionYear = "2019"
     description = "Compose Text primitives and utilities"
diff --git a/compose/ui/ui-text/lint-baseline.xml b/compose/ui/ui-text/lint-baseline.xml
index 48d11d8..cb16adc 100644
--- a/compose/ui/ui-text/lint-baseline.xml
+++ b/compose/ui/ui-text/lint-baseline.xml
@@ -1,104 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 28, the call containing class androidx.compose.ui.text.platform.AndroidDefaultTypeface is not annotated with @RequiresApi(x) where x is at least 28. Either annotate the containing class with at least @RequiresApi(28) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(28)."
-        errorLine1="            Typeface.create(Typeface.DEFAULT, fontWeight.weight, fontStyle == FontStyle.Italic)"
-        errorLine2="                     ~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidDefaultTypeface.android.kt"
-            line="44"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 28, the call containing class androidx.compose.ui.text.platform.AndroidGenericFontFamilyTypeface is not annotated with @RequiresApi(x) where x is at least 28. Either annotate the containing class with at least @RequiresApi(28) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(28)."
-        errorLine1="            Typeface.create(nativeTypeface, fontWeight.weight, fontStyle == FontStyle.Italic)"
-        errorLine2="                     ~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidGenericFontFamilyTypeface.android.kt"
-            line="75"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 28, the call containing class androidx.compose.ui.text.android.style.FontSpan is not annotated with @RequiresApi(x) where x is at least 28. Either annotate the containing class with at least @RequiresApi(28) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(28)."
-        errorLine1="            oldTypeface.weight"
-        errorLine2="                        ~~~~~~">
-        <location
-            file="../../../text/text/src/main/java/androidx/compose/ui/text/android/style/FontSpan.kt"
-            line="46"
-            column="25"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 28, the call containing class androidx.compose.ui.text.android.style.FontWeightStyleSpan is not annotated with @RequiresApi(x) where x is at least 28. Either annotate the containing class with at least @RequiresApi(28) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(28)."
-        errorLine1="            (weight != 0 &amp;&amp; weight != oldTypeface?.weight)"
-        errorLine2="                                                   ~~~~~~">
-        <location
-            file="../../../text/text/src/main/java/androidx/compose/ui/text/android/style/FontWeightStyleSpan.kt"
-            line="67"
-            column="52"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 28, the call containing class androidx.compose.ui.text.android.style.FontWeightStyleSpan is not annotated with @RequiresApi(x) where x is at least 28. Either annotate the containing class with at least @RequiresApi(28) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(28)."
-        errorLine1="            oldTypeface?.weight ?: FontStyle.FONT_WEIGHT_NORMAL"
-        errorLine2="                         ~~~~~~">
-        <location
-            file="../../../text/text/src/main/java/androidx/compose/ui/text/android/style/FontWeightStyleSpan.kt"
-            line="79"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 28, the call containing class androidx.compose.ui.text.android.style.FontWeightStyleSpan is not annotated with @RequiresApi(x) where x is at least 28. Either annotate the containing class with at least @RequiresApi(28) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(28)."
-        errorLine1="        textPaint.typeface = Typeface.create(oldTypeface, newWeight, newItalic)"
-        errorLine2="                                      ~~~~~~">
-        <location
-            file="../../../text/text/src/main/java/androidx/compose/ui/text/android/style/FontWeightStyleSpan.kt"
-            line="88"
-            column="39"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 28, the call containing class androidx.compose.ui.text.android.style.FontWeightStyleSpan is not annotated with @RequiresApi(x) where x is at least 28. Either annotate the containing class with at least @RequiresApi(28) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(28)."
-        errorLine1="        textPaint.typeface = Typeface.create(oldTypeface, newWeight, newItalic)"
-        errorLine2="                                      ~~~~~~">
-        <location
-            file="../../../text/text/src/main/java/androidx/compose/ui/text/android/style/FontWeightStyleSpan.kt"
-            line="88"
-            column="39"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 24, the call containing class androidx.compose.ui.text.platform.extensions.LocaleExtensions_androidKt is not annotated with @RequiresApi(x) where x is at least 24. Either annotate the containing class with at least @RequiresApi(24) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(24)."
-        errorLine1="    android.os.LocaleList(*map { it.toJavaLocale() }.toTypedArray())"
-        errorLine2="               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/LocaleExtensions.android.kt"
-            line="28"
-            column="16"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 24, the call containing class androidx.compose.ui.text.platform.extensions.SpannableExtensions_androidKt is not annotated with @RequiresApi(x) where x is at least 24. Either annotate the containing class with at least @RequiresApi(24) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(24)."
-        errorLine1="                LocaleSpan(it.toAndroidLocaleList())"
-        errorLine2="                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt"
-            line="280"
-            column="17"/>
-    </issue>
+<issues format="5" by="lint 4.2.0-beta04" client="gradle" variant="debug" version="4.2.0-beta04">
 
     <issue
         id="UnsafeNewApiCall"
@@ -254,37 +155,4 @@
             column="19"/>
     </issue>
 
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 24, the call containing class androidx.compose.ui.text.platform.extensions.TextPaintExtensions_androidKt is not annotated with @RequiresApi(x) where x is at least 24. Either annotate the containing class with at least @RequiresApi(24) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(24)."
-        errorLine1="            textLocales = style.localeList.toAndroidLocaleList()"
-        errorLine2="            ~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/TextPaintExtensions.android.kt"
-            line="59"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 28, the call containing class androidx.compose.ui.text.platform.TypefaceAdapter.Companion is not annotated with @RequiresApi(x) where x is at least 28. Either annotate the containing class with at least @RequiresApi(28) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(28)."
-        errorLine1="                Typeface.create(typeface, finalFontWeight, finalFontStyle)"
-        errorLine2="                         ~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/text/platform/TypefaceAdapter.android.kt"
-            line="99"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 28, the call containing class androidx.compose.ui.text.platform.TypefaceAdapter is not annotated with @RequiresApi(x) where x is at least 28. Either annotate the containing class with at least @RequiresApi(28) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(28)."
-        errorLine1="            Typeface.create("
-        errorLine2="                     ~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/text/platform/TypefaceAdapter.android.kt"
-            line="213"
-            column="22"/>
-    </issue>
-
 </issues>
diff --git a/compose/ui/ui-text/samples/lint-baseline.xml b/compose/ui/ui-text/samples/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/ui/ui-text/samples/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidFontListTypeface.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidFontListTypeface.android.kt
index fb47cea..733d5d2 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidFontListTypeface.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidFontListTypeface.android.kt
@@ -29,6 +29,7 @@
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.font.ResourceFont
 import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastMap
 import androidx.core.content.res.ResourcesCompat
 
 /**
@@ -48,9 +49,9 @@
     private val loadedTypefaces: Map<Font, Typeface>
 
     init {
-        val targetFonts = necessaryStyles?.map { (weight, style) ->
+        val targetFonts = necessaryStyles?.fastMap { (weight, style) ->
             fontMatcher.matchFont(fontFamily, weight, style)
-        }?.distinct() ?: fontFamily.fonts
+        }?.let { LinkedHashSet(it).toList() } ?: fontFamily.fonts
         val typefaces = mutableMapOf<Font, Typeface>()
 
         targetFonts.fastForEach {
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt
index 9f8339e..123023f 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/extensions/SpannableExtensions.android.kt
@@ -45,6 +45,7 @@
 import androidx.compose.ui.text.android.style.ShadowSpan
 import androidx.compose.ui.text.android.style.SkewXSpan
 import androidx.compose.ui.text.android.style.TypefaceSpan
+import androidx.compose.ui.text.fastFilter
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.font.FontStyle
 import androidx.compose.ui.text.font.FontSynthesis
@@ -149,7 +150,8 @@
     // applied after all spans that changes the fontSize.
     val lowPrioritySpans = ArrayList<SpanRange>()
 
-    for (spanStyleRange in spanStyles) {
+    for (i in spanStyles.indices) {
+        val spanStyleRange = spanStyles[i]
         val start = spanStyleRange.start
         val end = spanStyleRange.end
 
@@ -208,7 +210,7 @@
     spanStyles: List<AnnotatedString.Range<SpanStyle>>,
     typefaceAdapter: TypefaceAdapter
 ) {
-    val fontRelatedSpanStyles = spanStyles.filter {
+    val fontRelatedSpanStyles = spanStyles.fastFilter {
         it.item.hasFontAttributes() || it.item.fontSynthesis != null
     }
     flattenStylesAndApply(fontRelatedSpanStyles) { spanStyle, start, end ->
@@ -263,19 +265,19 @@
 
         // Check all spans that intersects with this transition range.
         var mergedSpanStyle: SpanStyle? = null
-        for (spanStyle in spanStyles) {
+        spanStyles.fastForEach { spanStyle ->
             if (
                 intersect(lastTransitionOffsets, transitionOffset, spanStyle.start, spanStyle.end)
             ) {
                 if (mergedSpanStyle == null) {
                     mergedSpanStyle = SpanStyle()
                 }
-                mergedSpanStyle = mergedSpanStyle.merge(spanStyle.item)
+                mergedSpanStyle = mergedSpanStyle!!.merge(spanStyle.item)
             }
         }
 
-        if (mergedSpanStyle != null) {
-            block(mergedSpanStyle, lastTransitionOffsets, transitionOffset)
+        mergedSpanStyle?.let {
+            block(it, lastTransitionOffsets, transitionOffset)
         }
 
         lastTransitionOffsets = transitionOffset
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 7cdc39c..2eed6f2 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
@@ -22,6 +22,7 @@
 import androidx.compose.ui.text.AnnotatedString.Range
 import androidx.compose.ui.text.intl.LocaleList
 import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastMap
 
 /**
  * The basic data structure of text with multiple styles. To construct an [AnnotatedString] you
@@ -133,7 +134,7 @@
      */
     @Suppress("UNCHECKED_CAST")
     fun getStringAnnotations(tag: String, start: Int, end: Int): List<Range<String>> =
-        annotations.filter {
+        annotations.fastFilter {
             it.item is String && tag == it.tag && intersect(start, end, it.start, it.end)
         } as List<Range<String>>
 
@@ -148,7 +149,7 @@
      */
     @Suppress("UNCHECKED_CAST")
     fun getStringAnnotations(start: Int, end: Int): List<Range<String>> =
-        annotations.filter {
+        annotations.fastFilter {
             it.item is String && intersect(start, end, it.start, it.end)
         } as List<Range<String>>
 
@@ -163,7 +164,7 @@
      */
     @Suppress("UNCHECKED_CAST")
     fun getTtsAnnotations(start: Int, end: Int): List<Range<TtsAnnotation>> =
-        annotations.filter {
+        annotations.fastFilter {
             it.item is TtsAnnotation && intersect(start, end, it.start, it.end)
         } as List<Range<TtsAnnotation>>
 
@@ -288,14 +289,14 @@
             val start = this.text.length
             this.text.append(text.text)
             // offset every style with start and add to the builder
-            text.spanStyles.forEach {
+            text.spanStyles.fastForEach {
                 addStyle(it.item, start + it.start, start + it.end)
             }
-            text.paragraphStyles.forEach {
+            text.paragraphStyles.fastForEach {
                 addStyle(it.item, start + it.start, start + it.end)
             }
 
-            text.annotations.forEach {
+            text.annotations.fastForEach {
                 annotations.add(
                     MutableRange(it.item, start + it.start, start + it.end, it.tag)
                 )
@@ -446,9 +447,9 @@
         fun toAnnotatedString(): AnnotatedString {
             return AnnotatedString(
                 text = text.toString(),
-                spanStyles = spanStyles.map { it.toRange(text.length) },
-                paragraphStyles = paragraphStyles.map { it.toRange(text.length) },
-                annotations = annotations.map { it.toRange(text.length) }
+                spanStyles = spanStyles.fastMap { it.toRange(text.length) },
+                paragraphStyles = paragraphStyles.fastMap { it.toRange(text.length) },
+                annotations = annotations.fastMap { it.toRange(text.length) }
             )
         }
     }
@@ -512,8 +513,8 @@
     if (start == 0 && end >= this.text.length) {
         return spanStyles
     }
-    return spanStyles.filter { intersect(start, end, it.start, it.end) }
-        .map {
+    return spanStyles.fastFilter { intersect(start, end, it.start, it.end) }
+        .fastMap {
             Range(
                 it.item,
                 it.start.coerceIn(start, end) - start,
@@ -548,7 +549,7 @@
         paragraphStyle: Range<ParagraphStyle>
     ) -> T
 ): List<T> {
-    return normalizedParagraphStyles(defaultParagraphStyle).map { paragraphStyleRange ->
+    return normalizedParagraphStyles(defaultParagraphStyle).fastMap { paragraphStyleRange ->
         val annotatedString = substringWithoutParagraphStyles(
             paragraphStyleRange.start,
             paragraphStyleRange.end
@@ -714,7 +715,7 @@
  */
 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.filter { intersect(start, end, it.start, it.end) }.map {
+    return ranges.fastFilter { intersect(start, end, it.start, it.end) }.fastMap {
         Range(
             item = it.item,
             start = maxOf(start, it.start) - start,
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/MultiParagraph.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/MultiParagraph.kt
index 6b48e80..886adf5 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/MultiParagraph.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/MultiParagraph.kt
@@ -27,6 +27,7 @@
 import androidx.compose.ui.text.style.TextDecoration
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastMap
 import kotlin.math.max
 
 /**
@@ -215,9 +216,9 @@
         this.didExceedMaxLines = didExceedMaxLines
         this.paragraphInfoList = paragraphInfoList
         this.width = width
-        this.placeholderRects = paragraphInfoList.flatMap { paragraphInfo ->
+        this.placeholderRects = paragraphInfoList.fastFlatMap { paragraphInfo ->
             with(paragraphInfo) {
-                paragraph.placeholderRects.map { it?.toGlobal() }
+                paragraph.placeholderRects.fastMap { it?.toGlobal() }
             }
         }.let {
             // When paragraphs get ellipsized, the size of this list will be smaller than
@@ -258,9 +259,9 @@
         val paragraphIndex = findParagraphByIndex(paragraphInfoList, start)
         val path = Path()
 
-        paragraphInfoList.drop(paragraphIndex)
-            .takeWhile { it.startIndex < end }
-            .filterNot { it.startIndex == it.endIndex }
+        paragraphInfoList.fastDrop(paragraphIndex)
+            .fastTakeWhile { it.startIndex < end }
+            .fastFilterNot { it.startIndex == it.endIndex }
             .fastForEach {
                 with(it) {
                     path.addPath(
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/MultiParagraphIntrinsics.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/MultiParagraphIntrinsics.kt
index 42a8fbe..2fd4c74 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/MultiParagraphIntrinsics.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/MultiParagraphIntrinsics.kt
@@ -19,6 +19,8 @@
 import androidx.compose.ui.text.font.Font
 import androidx.compose.ui.text.style.TextDirection
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.util.fastMap
+import androidx.compose.ui.util.fastMaxBy
 
 /**
  * Calculates and provides the intrinsic width and height of text that contains [ParagraphStyle].
@@ -46,13 +48,13 @@
 ) : ParagraphIntrinsics {
 
     override val minIntrinsicWidth: Float by lazy {
-        infoList.maxByOrNull {
+        infoList.fastMaxBy {
             it.intrinsics.minIntrinsicWidth
         }?.intrinsics?.minIntrinsicWidth ?: 0f
     }
 
     override val maxIntrinsicWidth: Float by lazy {
-        infoList.maxByOrNull {
+        infoList.fastMaxBy {
             it.intrinsics.maxIntrinsicWidth
         }?.intrinsics?.maxIntrinsicWidth ?: 0f
     }
@@ -109,7 +111,7 @@
 }
 
 private fun List<AnnotatedString.Range<Placeholder>>.getLocalPlaceholders(start: Int, end: Int) =
-    filter { intersect(start, end, it.start, it.end) }.map {
+    fastFilter { intersect(start, end, it.start, it.end) }.fastMap {
         require(start <= it.start && it.end <= end) {
             "placeholder can not overlap with paragraph."
         }
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TempListUtils.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TempListUtils.kt
new file mode 100644
index 0000000..79b7c59
--- /dev/null
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TempListUtils.kt
@@ -0,0 +1,224 @@
+/*
+ * 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.ui.text
+
+import androidx.compose.ui.util.fastForEach
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+
+// TODO: remove these when we can add new APIs to ui-util outside of beta cycle
+
+/**
+ * Returns a list containing only elements matching the given [predicate].
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T> List<T>.fastFilter(predicate: (T) -> Boolean): List<T> {
+    contract { callsInPlace(predicate) }
+    val target = ArrayList<T>(size)
+    fastForEach {
+        if (predicate(it)) target += (it)
+    }
+    return target
+}
+
+/**
+ * Returns a list containing only elements from the given collection
+ * having distinct keys returned by the given [selector] function.
+ *
+ * The elements in the resulting list are in the same order as they were in the source collection.
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T, K> List<T>.fastDistinctBy(selector: (T) -> K): List<T> {
+    contract { callsInPlace(selector) }
+    val set = HashSet<K>(size)
+    val target = ArrayList<T>(size)
+    fastForEach { e ->
+        val key = selector(e)
+        if (set.add(key)) target += e
+    }
+    return target
+}
+
+/**
+ * Returns the first element yielding the largest value of the given function or `null` if there
+ * are no elements.
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T, R : Comparable<R>> List<T>.fastMinByOrNull(selector: (T) -> R): T? {
+    contract { callsInPlace(selector) }
+    if (isEmpty()) return null
+    var minElem = get(0)
+    var minValue = selector(minElem)
+    for (i in 1..lastIndex) {
+        val e = get(i)
+        val v = selector(e)
+        if (minValue > v) {
+            minElem = e
+            minValue = v
+        }
+    }
+    return minElem
+}
+
+/**
+ * Accumulates value starting with [initial] value and applying [operation] from left to right
+ * to current accumulator value and each element.
+ *
+ * Returns the specified [initial] value if the collection is empty.
+ *
+ * @param [operation] function that takes current accumulator value and an element, and calculates the next accumulator value.
+ */
+@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
+}
+
+/**
+ * Returns a single list of all elements yielded from results of [transform] function being invoked
+ * on each element of original collection.
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T, R> List<T>.fastFlatMap(transform: (T) -> Iterable<R>): List<R> {
+    contract { callsInPlace(transform) }
+    val target = ArrayList<R>(size)
+    fastForEach { e ->
+        val list = transform(e)
+        target.addAll(list)
+    }
+    return target
+}
+
+/**
+ * Returns a list containing all elements not matching the given [predicate].
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T> List<T>.fastFilterNot(predicate: (T) -> Boolean): List<T> {
+    contract { callsInPlace(predicate) }
+    val target = ArrayList<T>(size)
+    fastForEach {
+        if (!predicate(it)) target += (it)
+    }
+    return target
+}
+
+/**
+ * Returns a list containing the first elements satisfying the given [predicate].
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T> List<T>.fastTakeWhile(predicate: (T) -> Boolean): List<T> {
+    contract { callsInPlace(predicate) }
+    val target = ArrayList<T>(size)
+    for (i in indices) {
+        val item = get(i)
+        if (!predicate(item))
+            break
+        target += item
+    }
+    return target
+}
+
+/**
+ * Returns a list containing all elements except first [n] elements.
+ *
+ * @throws IllegalArgumentException if [n] is negative.
+ */
+internal fun <T> List<T>.fastDrop(n: Int): List<T> {
+    require(n >= 0) { "Requested element count $n is less than zero." }
+    if (n == 0) {
+        return this
+    }
+    val resultSize = size - n
+    if (resultSize <= 0) {
+        return emptyList()
+    }
+    if (resultSize == 1) {
+        return listOf(last())
+    }
+    val target = ArrayList<T>(resultSize)
+    for (index in n until size) {
+        target += get(index)
+    }
+    return target
+}
+
+/**
+ * Creates a string from all the elements separated using [separator] and using the given [prefix]
+ * and [postfix] if supplied.
+ *
+ * If the collection could be huge, you can specify a non-negative value of [limit], in which case
+ * only the first [limit] elements will be appended, followed by the [truncated] string (which
+ * defaults to "...").
+ */
+internal fun <T> List<T>.fastJoinToString(
+    separator: CharSequence = ", ",
+    prefix: CharSequence = "",
+    postfix: CharSequence = "",
+    limit: Int = -1,
+    truncated: CharSequence = "...",
+    transform: ((T) -> CharSequence)? = null
+): String {
+    return fastJoinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform)
+        .toString()
+}
+
+/**
+ * Appends the string from all the elements separated using [separator] and using the given
+ * [prefix] and [postfix] if supplied.
+ *
+ * If the collection could be huge, you can specify a non-negative value of [limit], in which
+ * case only the first [limit] elements will be appended, followed by the [truncated] string
+ * (which defaults to "...").
+ */
+private fun <T, A : Appendable> List<T>.fastJoinTo(
+    buffer: A,
+    separator: CharSequence = ", ",
+    prefix: CharSequence = "",
+    postfix: CharSequence = "",
+    limit: Int = -1,
+    truncated: CharSequence = "...",
+    transform: ((T) -> CharSequence)? = null
+): A {
+    buffer.append(prefix)
+    var count = 0
+    for (index in indices) {
+        val element = get(index)
+        if (++count > 1) buffer.append(separator)
+        if (limit < 0 || count <= limit) {
+            buffer.appendElement(element, transform)
+        } else break
+    }
+    if (limit >= 0 && count > limit) buffer.append(truncated)
+    buffer.append(postfix)
+    return buffer
+}
+
+/**
+ * Copied from Appendable.kt
+ */
+private fun <T> Appendable.appendElement(element: T, transform: ((T) -> CharSequence)?) {
+    when {
+        transform != null -> append(transform(element))
+        element is CharSequence? -> append(element)
+        element is Char -> append(element)
+        else -> append(element.toString())
+    }
+}
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamily.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamily.kt
index e8792ae..80ae2fc 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamily.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamily.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
+import androidx.compose.ui.text.fastDistinctBy
 
 /**
  * The base class of the font families.
@@ -96,7 +97,7 @@
 ) : FileBasedFontFamily(), List<Font> by fonts {
     init {
         check(fonts.isNotEmpty()) { "At least one font should be passed to FontFamily" }
-        check(fonts.distinctBy { Pair(it.weight, it.style) }.size == fonts.size) {
+        check(fonts.fastDistinctBy { Pair(it.weight, it.style) }.size == fonts.size) {
             "There cannot be two fonts with the same FontWeight and FontStyle in the same " +
                 "FontFamily"
         }
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontMatcher.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontMatcher.kt
index eed9a3a..eebd4687 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontMatcher.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontMatcher.kt
@@ -16,6 +16,9 @@
 
 package androidx.compose.ui.text.font
 
+import androidx.compose.ui.text.fastMinByOrNull
+import androidx.compose.ui.util.fastMaxBy
+
 /**
  * Given a [FontFamily], [FontWeight] and [FontStyle], matches the best font in the [FontFamily]
  * that satisfies the requirements of [FontWeight] and [FontStyle].
@@ -58,14 +61,14 @@
             // If the desired weight is less than 400
             // - weights less than or equal to the desired weight are checked in descending order
             // - followed by weights above the desired weight in ascending order
-            fonts.filter { it.weight <= fontWeight }.maxByOrNull { it.weight }
-                ?: fonts.filter { it.weight > fontWeight }.minByOrNull { it.weight }
+            fonts.filter { it.weight <= fontWeight }.fastMaxBy { it.weight }
+                ?: fonts.filter { it.weight > fontWeight }.fastMinByOrNull { it.weight }
         } else if (fontWeight > FontWeight.W500) {
             // If the desired weight is greater than 500
             // - weights greater than or equal to the desired weight are checked in ascending order
             // - followed by weights below the desired weight in descending order
-            fonts.filter { it.weight >= fontWeight }.minByOrNull { it.weight }
-                ?: fonts.filter { it.weight < fontWeight }.maxByOrNull { it.weight }
+            fonts.filter { it.weight >= fontWeight }.fastMinByOrNull { it.weight }
+                ?: fonts.filter { it.weight < fontWeight }.fastMaxBy { it.weight }
         } else {
             // If the desired weight is inclusively between 400 and 500
             // - weights greater than or equal to the target weight are checked in ascending order
@@ -73,9 +76,9 @@
             // - followed by weights less than the target weight in descending order,
             // - followed by weights greater than 500
             fonts.filter { it.weight >= fontWeight && it.weight <= FontWeight.W500 }
-                .minByOrNull { it.weight }
-                ?: fonts.filter { it.weight < fontWeight }.maxByOrNull { it.weight }
-                ?: fonts.filter { it.weight > FontWeight.W500 }.minByOrNull { it.weight }
+                .fastMinByOrNull { it.weight }
+                ?: fonts.filter { it.weight < fontWeight }.fastMaxBy { it.weight }
+                ?: fonts.filter { it.weight > FontWeight.W500 }.fastMinByOrNull { it.weight }
         }
 
         return result ?: throw IllegalStateException("Cannot match any font")
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/EditProcessor.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/EditProcessor.kt
index 8c8a24a..f5c7559 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/EditProcessor.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/EditProcessor.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.emptyAnnotatedString
+import androidx.compose.ui.util.fastForEach
 
 /**
  * Helper class to apply [EditCommand]s on an internal buffer. Used by TextField Composable
@@ -89,7 +90,7 @@
      * @return the [TextFieldValue] representation of the final buffer state.
      */
     fun apply(editCommands: List<EditCommand>): TextFieldValue {
-        editCommands.forEach { it.applyTo(mBuffer) }
+        editCommands.fastForEach { it.applyTo(mBuffer) }
 
         val newState = TextFieldValue(
             annotatedString = mBuffer.toAnnotatedString(),
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/intl/LocaleList.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/intl/LocaleList.kt
index e5d8d4a..634f8bb 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/intl/LocaleList.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/intl/LocaleList.kt
@@ -19,6 +19,7 @@
 import androidx.compose.runtime.Immutable
 import androidx.compose.ui.text.SpanStyle
 import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.util.fastMap
 
 /**
  * Defines a list of [Locale] objects.
@@ -33,7 +34,7 @@
          * Returns Locale object which represents current locale
          */
         val current: LocaleList
-            get() = LocaleList(platformLocaleDelegate.current.map { Locale(it) })
+            get() = LocaleList(platformLocaleDelegate.current.fastMap { Locale(it) })
     }
 
     /**
@@ -43,7 +44,7 @@
      * compliant language tag.
      */
     constructor(languageTags: String) :
-        this(languageTags.split(",").map { it.trim() }.map { Locale(it) })
+        this(languageTags.split(",").fastMap { it.trim() }.fastMap { Locale(it) })
 
     /**
      * Creates a [LocaleList] object from a list of [Locale]s.
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/TextDecoration.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/TextDecoration.kt
index 2aeaa69..fe595ce 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/TextDecoration.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/TextDecoration.kt
@@ -17,6 +17,8 @@
 
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
+import androidx.compose.ui.text.fastFold
+import androidx.compose.ui.text.fastJoinToString
 
 /**
  * Defines a horizontal line to be drawn on the text.
@@ -52,7 +54,7 @@
          * @param decorations The decorations to be added
          */
         fun combine(decorations: List<TextDecoration>): TextDecoration {
-            val mask = decorations.fold(0) { acc, decoration ->
+            val mask = decorations.fastFold(0) { acc, decoration ->
                 acc or decoration.mask
             }
             return TextDecoration(mask)
@@ -92,7 +94,7 @@
         if ((values.size == 1)) {
             return "TextDecoration.${values[0]}"
         }
-        return "TextDecoration[${values.joinToString(separator = ", ")}]"
+        return "TextDecoration[${values.fastJoinToString(separator = ", ")}]"
     }
 
     override operator fun equals(other: Any?): Boolean {
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 4cf0652..d112fad 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
@@ -17,6 +17,7 @@
 package androidx.compose.ui.text
 
 import androidx.compose.ui.text.AnnotatedString.Range
+import androidx.compose.ui.util.fastMap
 
 import java.util.SortedSet
 
@@ -40,14 +41,14 @@
         offsetMap.put(end, resultStr.length)
     }
 
-    val newSpanStyles = spanStyles.map {
+    val newSpanStyles = spanStyles.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.map {
+    val newParaStyles = paragraphStyles.fastMap {
         Range(it.item, offsetMap[it.start]!!, offsetMap[it.end]!!)
     }
-    val newAnnotations = annotations.map {
+    val newAnnotations = annotations.fastMap {
         Range(it.item, offsetMap[it.start]!!, offsetMap[it.end]!!)
     }
 
@@ -69,7 +70,7 @@
     ranges: List<Range<T>>,
     target: SortedSet<Int>
 ) {
-    ranges.fold(target) { acc, range ->
+    ranges.fastFold(target) { acc, range ->
         acc.apply {
             add(range.start)
             add(range.end)
diff --git a/compose/ui/ui-tooling-data/build.gradle b/compose/ui/ui-tooling-data/build.gradle
index 236133a..6b6fc64 100644
--- a/compose/ui/ui-tooling-data/build.gradle
+++ b/compose/ui/ui-tooling-data/build.gradle
@@ -15,7 +15,7 @@
  */
 
 import androidx.build.LibraryGroups
-import androidx.build.Publish
+import androidx.build.LibraryType
 
 import static androidx.build.dependencies.DependenciesKt.*
 
@@ -51,7 +51,7 @@
 
 androidx {
     name = "Compose Tooling Data"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.UI
     inceptionYear = "2021"
     description = "Compose tooling library data. This library provides data about compose" +
diff --git a/compose/ui/ui-tooling-data/lint-baseline.xml b/compose/ui/ui-tooling-data/lint-baseline.xml
deleted file mode 100644
index 8f1aa4b..0000000
--- a/compose/ui/ui-tooling-data/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-</issues>
diff --git a/compose/ui/ui-tooling/OWNERS b/compose/ui/ui-tooling/OWNERS
index 36d6fce..1211f02 100644
--- a/compose/ui/ui-tooling/OWNERS
+++ b/compose/ui/ui-tooling/OWNERS
@@ -1,4 +1,5 @@
 [email protected]
 [email protected]
 [email protected]
[email protected]
\ No newline at end of file
[email protected]
[email protected]
\ No newline at end of file
diff --git a/compose/ui/ui-tooling/build.gradle b/compose/ui/ui-tooling/build.gradle
index 1fdfb45..ed238dd 100644
--- a/compose/ui/ui-tooling/build.gradle
+++ b/compose/ui/ui-tooling/build.gradle
@@ -15,7 +15,7 @@
  */
 
 import androidx.build.LibraryGroups
-import androidx.build.Publish
+import androidx.build.LibraryType
 
 import static androidx.build.dependencies.DependenciesKt.*
 
@@ -57,7 +57,7 @@
 
 androidx {
     name = "Compose Tooling"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.UI
     inceptionYear = "2019"
     description = "Compose tooling library. This library exposes information to our tools for better IDE support."
diff --git a/compose/ui/ui-tooling/lint-baseline.xml b/compose/ui/ui-tooling/lint-baseline.xml
index 9d534f57..aeb18ac 100644
--- a/compose/ui/ui-tooling/lint-baseline.xml
+++ b/compose/ui/ui-tooling/lint-baseline.xml
@@ -1,26 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
+<issues format="5" by="lint 4.2.0-beta04" client="gradle" variant="debug" version="4.2.0-beta04">
 
     <issue
         id="BanUncheckedReflection"
diff --git a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/preview/ComposeViewAdapter.kt b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/preview/ComposeViewAdapter.kt
index 8ed0973..1d76fe3 100644
--- a/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/preview/ComposeViewAdapter.kt
+++ b/compose/ui/ui-tooling/src/main/java/androidx/compose/ui/tooling/preview/ComposeViewAdapter.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.tooling.preview
 
+import android.annotation.SuppressLint
 import android.content.Context
 import android.graphics.Canvas
 import android.graphics.DashPathEffect
@@ -663,8 +664,9 @@
         )
     }
 
+    @SuppressLint("VisibleForTests")
     private val FakeSavedStateRegistryOwner = object : SavedStateRegistryOwner {
-        private val lifecycle = LifecycleRegistry(this)
+        private val lifecycle = LifecycleRegistry.createUnsafe(this)
         private val controller = SavedStateRegistryController.create(this).apply {
             performRestore(Bundle())
         }
diff --git a/compose/ui/ui-unit/build.gradle b/compose/ui/ui-unit/build.gradle
index 58994dd..f5341c6 100644
--- a/compose/ui/ui-unit/build.gradle
+++ b/compose/ui/ui-unit/build.gradle
@@ -17,7 +17,7 @@
 
 import androidx.build.AndroidXUiPlugin
 import androidx.build.LibraryGroups
-import androidx.build.Publish
+import androidx.build.LibraryType
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 import static androidx.build.dependencies.DependenciesKt.*
@@ -99,7 +99,7 @@
 
 androidx {
     name = "Compose Unit"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.UI
     inceptionYear = "2020"
     description = "Compose classes for simple units"
diff --git a/compose/ui/ui-unit/lint-baseline.xml b/compose/ui/ui-unit/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/ui/ui-unit/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/ui/ui-unit/samples/lint-baseline.xml b/compose/ui/ui-unit/samples/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/ui/ui-unit/samples/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/ui/ui-util/build.gradle b/compose/ui/ui-util/build.gradle
index aae1bab..d7f27ba 100644
--- a/compose/ui/ui-util/build.gradle
+++ b/compose/ui/ui-util/build.gradle
@@ -17,7 +17,7 @@
 
 import androidx.build.AndroidXUiPlugin
 import androidx.build.LibraryGroups
-import androidx.build.Publish
+import androidx.build.LibraryType
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 import static androidx.build.dependencies.DependenciesKt.*
@@ -37,10 +37,7 @@
          * corresponding block below
          */
 
-        api("androidx.annotation:annotation:1.1.0")
-
         implementation(KOTLIN_STDLIB)
-        implementation(project(":compose:runtime:runtime"))
 
         testImplementation(JUNIT)
         testImplementation(TRUTH)
@@ -60,8 +57,6 @@
         sourceSets {
             commonMain.dependencies {
                 implementation(KOTLIN_STDLIB_COMMON)
-
-                implementation(project(":compose:runtime:runtime"))
             }
 
             jvmMain.dependencies {
@@ -69,7 +64,7 @@
             }
 
             androidMain.dependencies {
-                api("androidx.annotation:annotation:1.1.0")
+                implementation(KOTLIN_STDLIB)
             }
 
             desktopMain.dependsOn(jvmMain)
@@ -87,7 +82,7 @@
 
 androidx {
     name = "Compose Util"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.UI
     inceptionYear = "2020"
     description = "Internal Compose utilities used by other modules"
diff --git a/compose/ui/ui-util/lint-baseline.xml b/compose/ui/ui-util/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/ui/ui-util/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/ListUtils.kt b/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/ListUtils.kt
index 5009fad..bf2f53d 100644
--- a/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/ListUtils.kt
+++ b/compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/ListUtils.kt
@@ -103,6 +103,7 @@
     return target
 }
 
+// TODO: should be fastMaxByOrNull to match stdlib
 /**
  * Returns the first element yielding the largest value of the given function or `null` if there
  * are no elements.
diff --git a/compose/ui/ui-viewbinding/build.gradle b/compose/ui/ui-viewbinding/build.gradle
index 1351db4..9a0d080 100644
--- a/compose/ui/ui-viewbinding/build.gradle
+++ b/compose/ui/ui-viewbinding/build.gradle
@@ -15,7 +15,7 @@
  */
 
 import androidx.build.LibraryGroups
-import androidx.build.Publish
+import androidx.build.LibraryType
 
 import static androidx.build.dependencies.DependenciesKt.*
 
@@ -41,7 +41,7 @@
 
 androidx {
     name = "Compose ViewBinding"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.UI
     inceptionYear = "2020"
     description = "Compose integration with ViewBinding"
diff --git a/compose/ui/ui-viewbinding/lint-baseline.xml b/compose/ui/ui-viewbinding/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/ui/ui-viewbinding/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/ui/ui-viewbinding/samples/lint-baseline.xml b/compose/ui/ui-viewbinding/samples/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/ui/ui-viewbinding/samples/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/ui/ui/api/1.0.0-beta02.txt b/compose/ui/ui/api/1.0.0-beta02.txt
index ee32b02..27ee819 100644
--- a/compose/ui/ui/api/1.0.0-beta02.txt
+++ b/compose/ui/ui/api/1.0.0-beta02.txt
@@ -139,6 +139,9 @@
     method public default <R> R! foldOut(R? initial, kotlin.jvm.functions.Function2<? super androidx.compose.ui.Modifier.Element,? super R,? extends R> operation);
   }
 
+  public final class TempListUtilsKt {
+  }
+
   public final class ZIndexModifierKt {
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier zIndex(androidx.compose.ui.Modifier, float zIndex);
   }
@@ -1873,6 +1876,9 @@
     property protected boolean shouldCreateCompositionOnAttachedToWindow;
   }
 
+  public final class ComposeView_androidKt {
+  }
+
   public final class CompositionLocalsKt {
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.AccessibilityManager> getLocalAccessibilityManager();
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.ClipboardManager> getLocalClipboardManager();
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index ee32b02..27ee819 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -139,6 +139,9 @@
     method public default <R> R! foldOut(R? initial, kotlin.jvm.functions.Function2<? super androidx.compose.ui.Modifier.Element,? super R,? extends R> operation);
   }
 
+  public final class TempListUtilsKt {
+  }
+
   public final class ZIndexModifierKt {
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier zIndex(androidx.compose.ui.Modifier, float zIndex);
   }
@@ -1873,6 +1876,9 @@
     property protected boolean shouldCreateCompositionOnAttachedToWindow;
   }
 
+  public final class ComposeView_androidKt {
+  }
+
   public final class CompositionLocalsKt {
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.AccessibilityManager> getLocalAccessibilityManager();
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.ClipboardManager> getLocalClipboardManager();
diff --git a/compose/ui/ui/api/public_plus_experimental_1.0.0-beta02.txt b/compose/ui/ui/api/public_plus_experimental_1.0.0-beta02.txt
index c23776f..8c2e1ce 100644
--- a/compose/ui/ui/api/public_plus_experimental_1.0.0-beta02.txt
+++ b/compose/ui/ui/api/public_plus_experimental_1.0.0-beta02.txt
@@ -145,6 +145,9 @@
     method public default <R> R! foldOut(R? initial, kotlin.jvm.functions.Function2<? super androidx.compose.ui.Modifier.Element,? super R,? extends R> operation);
   }
 
+  public final class TempListUtilsKt {
+  }
+
   public final class ZIndexModifierKt {
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier zIndex(androidx.compose.ui.Modifier, float zIndex);
   }
@@ -1976,6 +1979,9 @@
     property protected boolean shouldCreateCompositionOnAttachedToWindow;
   }
 
+  public final class ComposeView_androidKt {
+  }
+
   public final class CompositionLocalsKt {
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.AccessibilityManager> getLocalAccessibilityManager();
     method @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.autofill.Autofill> getLocalAutofill();
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index c23776f..8c2e1ce 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -145,6 +145,9 @@
     method public default <R> R! foldOut(R? initial, kotlin.jvm.functions.Function2<? super androidx.compose.ui.Modifier.Element,? super R,? extends R> operation);
   }
 
+  public final class TempListUtilsKt {
+  }
+
   public final class ZIndexModifierKt {
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier zIndex(androidx.compose.ui.Modifier, float zIndex);
   }
@@ -1976,6 +1979,9 @@
     property protected boolean shouldCreateCompositionOnAttachedToWindow;
   }
 
+  public final class ComposeView_androidKt {
+  }
+
   public final class CompositionLocalsKt {
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.AccessibilityManager> getLocalAccessibilityManager();
     method @androidx.compose.ui.ExperimentalComposeUiApi public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.autofill.Autofill> getLocalAutofill();
diff --git a/compose/ui/ui/api/restricted_1.0.0-beta02.txt b/compose/ui/ui/api/restricted_1.0.0-beta02.txt
index 15bf270..8d26e88 100644
--- a/compose/ui/ui/api/restricted_1.0.0-beta02.txt
+++ b/compose/ui/ui/api/restricted_1.0.0-beta02.txt
@@ -139,6 +139,9 @@
     method public default <R> R! foldOut(R? initial, kotlin.jvm.functions.Function2<? super androidx.compose.ui.Modifier.Element,? super R,? extends R> operation);
   }
 
+  public final class TempListUtilsKt {
+  }
+
   public final class ZIndexModifierKt {
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier zIndex(androidx.compose.ui.Modifier, float zIndex);
   }
@@ -1903,6 +1906,9 @@
     property protected boolean shouldCreateCompositionOnAttachedToWindow;
   }
 
+  public final class ComposeView_androidKt {
+  }
+
   public final class CompositionLocalsKt {
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.AccessibilityManager> getLocalAccessibilityManager();
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.ClipboardManager> getLocalClipboardManager();
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 15bf270..8d26e88 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -139,6 +139,9 @@
     method public default <R> R! foldOut(R? initial, kotlin.jvm.functions.Function2<? super androidx.compose.ui.Modifier.Element,? super R,? extends R> operation);
   }
 
+  public final class TempListUtilsKt {
+  }
+
   public final class ZIndexModifierKt {
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier zIndex(androidx.compose.ui.Modifier, float zIndex);
   }
@@ -1903,6 +1906,9 @@
     property protected boolean shouldCreateCompositionOnAttachedToWindow;
   }
 
+  public final class ComposeView_androidKt {
+  }
+
   public final class CompositionLocalsKt {
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.AccessibilityManager> getLocalAccessibilityManager();
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.platform.ClipboardManager> getLocalClipboardManager();
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index a4de7229..52ffcc4 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -17,7 +17,7 @@
 
 import androidx.build.AndroidXUiPlugin
 import androidx.build.LibraryGroups
-import androidx.build.Publish
+import androidx.build.LibraryType
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 
 import static androidx.build.dependencies.DependenciesKt.*
@@ -226,7 +226,7 @@
 
 androidx {
     name = "Compose UI primitives"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    type = LibraryType.PUBLISHED_LIBRARY
     mavenGroup = LibraryGroups.Compose.UI
     inceptionYear = "2019"
     description = "Compose UI primitives. This library contains the primitives that form the Compose UI Toolkit, such as drawing, measurement and layout."
diff --git a/compose/ui/ui/integration-tests/ui-demos/lint-baseline.xml b/compose/ui/ui/integration-tests/ui-demos/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/ui/ui/integration-tests/ui-demos/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/VerticalScrollerInDrawerLayoutDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/VerticalScrollerInDrawerLayoutDemo.kt
index 54f7016..192a5b5 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/VerticalScrollerInDrawerLayoutDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/VerticalScrollerInDrawerLayoutDemo.kt
@@ -98,9 +98,7 @@
             }
         )
     ) {
-        Column {
-            content()
-        }
+        Column(content = content)
         Box(
             Modifier
                 .fillMaxHeight()
diff --git a/compose/ui/ui/lint-baseline.xml b/compose/ui/ui/lint-baseline.xml
index 1df8ee47..39666a2 100644
--- a/compose/ui/ui/lint-baseline.xml
+++ b/compose/ui/ui/lint-baseline.xml
@@ -1,26 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
+<issues format="5" by="lint 4.2.0-beta04" client="gradle" variant="debug" version="4.2.0-beta04">
 
     <issue
         id="BanTargetApiAnnotation"
@@ -40,7 +19,7 @@
         errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt"
-            line="719"
+            line="815"
             column="13"/>
     </issue>
 
@@ -51,161 +30,18 @@
         errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="src/androidMain/kotlin/androidx/compose/ui/platform/ViewLayer.android.kt"
-            line="285"
+            line="288"
             column="13"/>
     </issue>
 
     <issue
         id="UnsafeNewApiCall"
-        message="This call is to a method from API 23, the call containing class androidx.compose.ui.autofill.AndroidAutofill_androidKt is not annotated with @RequiresApi(x) where x is at least 23. Either annotate the containing class with at least @RequiresApi(23) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(23)."
-        errorLine1="    var index = root.addChildCount(autofillTree.children.count())"
-        errorLine2="                     ~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt"
-            line="73"
-            column="22"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 23, the call containing class androidx.compose.ui.autofill.AndroidAutofill_androidKt is not annotated with @RequiresApi(x) where x is at least 23. Either annotate the containing class with at least @RequiresApi(23) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(23)."
-        errorLine1="        root.newChild(index)?.apply {"
-        errorLine2="             ~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt"
-            line="76"
-            column="14"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 26, the call containing class androidx.compose.ui.autofill.AndroidAutofill_androidKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
-        errorLine1="            setAutofillId(root.autofillId!!, id)"
-        errorLine2="            ~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt"
-            line="77"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 26, the call containing class androidx.compose.ui.autofill.AndroidAutofill_androidKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
-        errorLine1="            setAutofillId(root.autofillId!!, id)"
-        errorLine2="                               ~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt"
-            line="77"
-            column="32"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 23, the call containing class androidx.compose.ui.autofill.AndroidAutofill_androidKt is not annotated with @RequiresApi(x) where x is at least 23. Either annotate the containing class with at least @RequiresApi(23) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(23)."
-        errorLine1="            setId(id, view.context.packageName, null, null)"
-        errorLine2="            ~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt"
-            line="78"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 26, the call containing class androidx.compose.ui.autofill.AndroidAutofill_androidKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
-        errorLine1="            setAutofillType(View.AUTOFILL_TYPE_TEXT)"
-        errorLine2="            ~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt"
-            line="79"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 26, the call containing class androidx.compose.ui.autofill.AndroidAutofill_androidKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
-        errorLine1="            setAutofillHints(autofillNode.autofillTypes.map { it.androidType }.toTypedArray())"
-        errorLine2="            ~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt"
-            line="80"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 23, the call containing class androidx.compose.ui.autofill.AndroidAutofill_androidKt is not annotated with @RequiresApi(x) where x is at least 23. Either annotate the containing class with at least @RequiresApi(23) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(23)."
-        errorLine1="                setDimens(left, top, 0, 0, width(), height())"
-        errorLine2="                ~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt"
-            line="92"
-            column="17"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 26, the call containing class androidx.compose.ui.autofill.AndroidAutofill_androidKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
-        errorLine1="            value.isText -> autofillTree.performAutofill(itemId, value.textValue.toString())"
-        errorLine2="                  ~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt"
-            line="109"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 26, the call containing class androidx.compose.ui.autofill.AndroidAutofill_androidKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
-        errorLine1="            value.isText -> autofillTree.performAutofill(itemId, value.textValue.toString())"
-        errorLine2="                                                                       ~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt"
-            line="109"
-            column="72"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 26, the call containing class androidx.compose.ui.autofill.AndroidAutofill_androidKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
-        errorLine1="            value.isDate -> TODO(&quot;b/138604541: Add onFill() callback for date&quot;)"
-        errorLine2="                  ~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt"
-            line="110"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 26, the call containing class androidx.compose.ui.autofill.AndroidAutofill_androidKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
-        errorLine1="            value.isList -> TODO(&quot;b/138604541: Add onFill() callback for list&quot;)"
-        errorLine2="                  ~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt"
-            line="111"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 26, the call containing class androidx.compose.ui.autofill.AndroidAutofill_androidKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
-        errorLine1="            value.isToggle -> TODO(&quot;b/138604541:  Add onFill() callback for toggle&quot;)"
-        errorLine2="                  ~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt"
-            line="112"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
         message="This call is to a method from API 26, the call containing class androidx.compose.ui.autofill.AndroidAutofillDebugUtils_androidKt is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
         errorLine1="    autofillManager.registerCallback(AutofillCallback)"
         errorLine2="                    ~~~~~~~~~~~~~~~~">
         <location
             file="src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillDebugUtils.android.kt"
-            line="65"
+            line="67"
             column="21"/>
     </issue>
 
@@ -216,56 +52,12 @@
         errorLine2="                    ~~~~~~~~~~~~~~~~~~">
         <location
             file="src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofillDebugUtils.android.kt"
-            line="74"
+            line="77"
             column="21"/>
     </issue>
 
     <issue
         id="UnsafeNewApiCall"
-        message="This call is to a method from API 26, the call containing class androidx.compose.ui.platform.AndroidComposeView is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
-        errorLine1="            focusable = View.FOCUSABLE"
-        errorLine2="            ~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt"
-            line="280"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 26, the call containing class androidx.compose.ui.platform.AndroidComposeView is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
-        errorLine1="            defaultFocusHighlightEnabled = false"
-        errorLine2="            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt"
-            line="282"
-            column="13"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 26, the call containing class androidx.compose.ui.platform.AndroidComposeViewAccessibilityDelegateCompat is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
-        errorLine1="            info.unwrap().availableExtraData = listOf(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)"
-        errorLine2="                          ~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt"
-            line="407"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 23, the call containing class androidx.compose.ui.platform.AndroidTextToolbar is not annotated with @RequiresApi(x) where x is at least 23. Either annotate the containing class with at least @RequiresApi(23) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(23)."
-        errorLine1="            actionMode = view.startActionMode("
-        errorLine2="                              ~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/platform/AndroidTextToolbar.android.kt"
-            line="53"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
         message="This call is to a method from API 23, the call containing class androidx.compose.ui.res.ColorResources_androidKt is not annotated with @RequiresApi(x) where x is at least 23. Either annotate the containing class with at least @RequiresApi(23) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(23)."
         errorLine1="        Color(context.resources.getColor(id, context.theme))"
         errorLine2="                                ~~~~~~~~">
@@ -286,26 +78,4 @@
             column="5"/>
     </issue>
 
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 26, the call containing class androidx.compose.ui.platform.RenderNodeLayer is not annotated with @RequiresApi(x) where x is at least 26. Either annotate the containing class with at least @RequiresApi(26) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(26)."
-        errorLine1="            ownerView.parent?.onDescendantInvalidated(ownerView, ownerView)"
-        errorLine2="                              ~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.android.kt"
-            line="165"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 29, the call containing class androidx.compose.ui.platform.Wrapper_androidKt is not annotated with @RequiresApi(x) where x is at least 29. Either annotate the containing class with at least @RequiresApi(29) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(29)."
-        errorLine1="        owner.attributeSourceResourceMap.isNotEmpty()"
-        errorLine2="              ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/androidMain/kotlin/androidx/compose/ui/platform/Wrapper.android.kt"
-            line="308"
-            column="15"/>
-    </issue>
-
 </issues>
diff --git a/compose/ui/ui/samples/lint-baseline.xml b/compose/ui/ui/samples/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/compose/ui/ui/samples/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LayoutSample.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LayoutSample.kt
index d3aca71..656c402 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LayoutSample.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/LayoutSample.kt
@@ -46,8 +46,16 @@
         val childConstraints = Constraints(
             minWidth = constraints.minWidth / 2,
             minHeight = constraints.minHeight / 2,
-            maxWidth = constraints.maxWidth / 2,
-            maxHeight = constraints.maxHeight / 2
+            maxWidth = if (constraints.hasBoundedWidth) {
+                constraints.maxWidth / 2
+            } else {
+                Constraints.Infinity
+            },
+            maxHeight = if (constraints.hasBoundedHeight) {
+                constraints.maxHeight / 2
+            } else {
+                Constraints.Infinity
+            }
         )
         // We measure the children with half our constraints, to ensure we can be double
         // the size of the children.
@@ -79,8 +87,16 @@
             val childConstraints = Constraints(
                 minWidth = constraints.minWidth / 2,
                 minHeight = constraints.minHeight / 2,
-                maxWidth = constraints.maxWidth / 2,
-                maxHeight = constraints.maxHeight / 2
+                maxWidth = if (constraints.hasBoundedWidth) {
+                    constraints.maxWidth / 2
+                } else {
+                    Constraints.Infinity
+                },
+                maxHeight = if (constraints.hasBoundedHeight) {
+                    constraints.maxHeight / 2
+                } else {
+                    Constraints.Infinity
+                }
             )
             // We measure the children with half our constraints, to ensure we can be double
             // the size of the children.
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
index 24ee32c..e9dcb07 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
@@ -30,26 +30,35 @@
 import android.view.accessibility.AccessibilityRecord
 import androidx.activity.ComponentActivity
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.selection.toggleable
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.platform.AndroidComposeView
 import androidx.compose.ui.platform.AndroidComposeViewAccessibilityDelegateCompat
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.SemanticsActions
 import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.paneTitle
+import androidx.compose.ui.semantics.role
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.textSelectionRange
 import androidx.compose.ui.test.SemanticsMatcher
@@ -67,6 +76,7 @@
 import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.input.PasswordVisualTransformation
 import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.Dialog
 import androidx.core.view.ViewCompat
@@ -1174,6 +1184,307 @@
         }
     }
 
+    @Test
+    fun testContentDescription_notMergingDescendants_withOwnContentDescription() {
+        val tag = "Column"
+        container.setContent {
+            Column(Modifier.semantics { contentDescription = "Column" }.testTag(tag)) {
+                BasicText("Text")
+                Box(Modifier.size(100.dp).semantics { contentDescription = "Box" })
+            }
+        }
+
+        val node = rule.onNodeWithTag(tag).fetchSemanticsNode()
+        val info = provider.createAccessibilityNodeInfo(node.id)
+
+        assertEquals("Column", info.contentDescription)
+    }
+
+    @Test
+    fun testContentDescription_mergingDescendants_withOwnContentDescription() {
+        val tag = "Column"
+        container.setContent {
+            Column(Modifier.semantics(true) { contentDescription = "Column" }.testTag(tag)) {
+                BasicText("Text")
+                Box(Modifier.size(100.dp).semantics { contentDescription = "Box" })
+            }
+        }
+
+        val node = rule.onNodeWithTag(tag).fetchSemanticsNode()
+        val info = provider.createAccessibilityNodeInfo(node.id)
+
+        assertEquals("Column", info.contentDescription)
+    }
+
+    @Test
+    fun testContentDescription_notMergingDescendants_withoutOwnContentDescription() {
+        val tag = "Column"
+        container.setContent {
+            Column(Modifier.semantics {}.testTag(tag)) {
+                BasicText("Text")
+                Box(Modifier.size(100.dp).semantics { contentDescription = "Box" })
+            }
+        }
+
+        val node = rule.onNodeWithTag(tag).fetchSemanticsNode()
+        val info = provider.createAccessibilityNodeInfo(node.id)
+
+        assertEquals(null, info.contentDescription)
+    }
+
+    @Test
+    fun testContentDescription_mergingDescendants_withoutOwnContentDescription() {
+        val tag = "Column"
+        container.setContent {
+            Column(Modifier.semantics(true) {}.testTag(tag)) {
+                BasicText("Text")
+                Box(Modifier.size(100.dp).semantics { contentDescription = "Box" })
+            }
+        }
+
+        val node = rule.onNodeWithTag(tag).fetchSemanticsNode()
+        val info = provider.createAccessibilityNodeInfo(node.id)
+
+        assertEquals("Text, Box", info.contentDescription)
+    }
+
+    @Test
+    fun testContentDescription_mergingDescendants() {
+        // This is a bit more complex example
+        val tag = "Column"
+        container.setContent {
+            Column(Modifier.semantics(true) {}.testTag(tag)) {
+                Column(Modifier.semantics(true) { contentDescription = "Column1" }) {
+                    BasicText("Text1")
+                    Row(Modifier.semantics {}) {
+                        Box(Modifier.size(100.dp).semantics { contentDescription = "Box1" })
+                        Box(Modifier.size(100.dp).semantics { contentDescription = "Box2" })
+                    }
+                }
+                Column(Modifier.semantics {}) {
+                    BasicText("Text2")
+                    Row(Modifier.semantics(true) {}) {
+                        Box(Modifier.size(100.dp).semantics { contentDescription = "Box3" })
+                        Box(Modifier.size(100.dp).semantics { contentDescription = "Box4" })
+                    }
+                }
+                Column(Modifier.semantics { }) {
+                    BasicText("Text3")
+                    Row(Modifier.semantics {}) {
+                        Box(Modifier.size(100.dp).semantics { contentDescription = "Box5" })
+                        Box(Modifier.size(100.dp).semantics { contentDescription = "Box6" })
+                    }
+                }
+            }
+        }
+
+        val node = rule.onNodeWithTag(tag).fetchSemanticsNode()
+        val info = provider.createAccessibilityNodeInfo(node.id)
+
+        assertEquals("Text2, Text3, Box5, Box6", info.contentDescription)
+    }
+
+    @Test
+    fun testRole_doesNotMerge() {
+        container.setContent {
+            Row(Modifier.semantics(true) {}.testTag("Row")) {
+                Box(Modifier.size(100.dp).semantics { role = Role.Button })
+                Box(Modifier.size(100.dp).semantics { role = Role.Image })
+            }
+        }
+
+        val node = rule.onNodeWithTag("Row").fetchSemanticsNode()
+        val info = provider.createAccessibilityNodeInfo(node.id)
+
+        assertEquals(AndroidComposeViewAccessibilityDelegateCompat.ClassName, info.className)
+    }
+
+    @Test
+    fun testReportedBounds_clickableNode_includesPadding() {
+        val size = 100.dp
+        container.setContent {
+            Column {
+                Box(
+                    Modifier
+                        .testTag("tag")
+                        .clickable {}
+                        .size(size)
+                        .padding(10.dp)
+                        .semantics {
+                            contentDescription = "Button"
+                        }
+                )
+            }
+        }
+
+        val node = rule.onNodeWithTag("tag").fetchSemanticsNode()
+        val accessibilityNodeInfo = provider.createAccessibilityNodeInfo(node.id)
+
+        val rect = android.graphics.Rect()
+        accessibilityNodeInfo.getBoundsInScreen(rect)
+        val resultWidth = rect.right - rect.left
+        val resultHeight = rect.bottom - rect.top
+
+        with(rule.density) {
+            assertEquals(size.roundToPx(), resultWidth)
+            assertEquals(size.roundToPx(), resultHeight)
+        }
+    }
+
+    @Test
+    fun testReportedBounds_clickableNode_excludesPadding() {
+        val size = 100.dp
+        val density = Density(2f)
+        container.setContent {
+            CompositionLocalProvider(LocalDensity provides density) {
+                Column {
+                    Box(
+                        Modifier
+                            .testTag("tag")
+                            .semantics { contentDescription = "Test" }
+                            .size(size)
+                            .padding(10.dp)
+                            .clickable {}
+                    )
+                }
+            }
+        }
+
+        val node = rule.onNodeWithTag("tag").fetchSemanticsNode()
+        val accessibilityNodeInfo = provider.createAccessibilityNodeInfo(node.id)
+
+        val rect = android.graphics.Rect()
+        accessibilityNodeInfo.getBoundsInScreen(rect)
+        val resultWidth = rect.right - rect.left
+        val resultHeight = rect.bottom - rect.top
+
+        with(density) {
+            assertEquals((size - 20.dp).roundToPx(), resultWidth)
+            assertEquals((size - 20.dp).roundToPx(), resultHeight)
+        }
+    }
+
+    @Test
+    fun testReportedBounds_withClearAndSetSemantics() {
+        val size = 100.dp
+        container.setContent {
+            Column {
+                Box(
+                    Modifier
+                        .testTag("tag")
+                        .size(size)
+                        .padding(10.dp)
+                        .clearAndSetSemantics {}
+                        .clickable {}
+                )
+            }
+        }
+
+        val node = rule.onNodeWithTag("tag").fetchSemanticsNode()
+        val accessibilityNodeInfo = provider.createAccessibilityNodeInfo(node.id)
+
+        val rect = android.graphics.Rect()
+        accessibilityNodeInfo.getBoundsInScreen(rect)
+        val resultWidth = rect.right - rect.left
+        val resultHeight = rect.bottom - rect.top
+
+        with(rule.density) {
+            assertEquals(size.roundToPx(), resultWidth)
+            assertEquals(size.roundToPx(), resultHeight)
+        }
+    }
+
+    @Test
+    fun testReportedBounds_withTwoClickable_outermostWins() {
+        val size = 100.dp
+        container.setContent {
+            Column {
+                Box(
+                    Modifier
+                        .testTag("tag")
+                        .clickable {}
+                        .size(size)
+                        .padding(10.dp)
+                        .clickable {}
+                )
+            }
+        }
+
+        val node = rule.onNodeWithTag("tag").fetchSemanticsNode()
+        val accessibilityNodeInfo = provider.createAccessibilityNodeInfo(node.id)
+
+        val rect = android.graphics.Rect()
+        accessibilityNodeInfo.getBoundsInScreen(rect)
+        val resultWidth = rect.right - rect.left
+        val resultHeight = rect.bottom - rect.top
+
+        with(rule.density) {
+            assertEquals(size.roundToPx(), resultWidth)
+            assertEquals(size.roundToPx(), resultHeight)
+        }
+    }
+
+    @Test
+    fun testReportedBounds_outerMostSemanticsUsed() {
+        val size = 100.dp
+        container.setContent {
+            Column {
+                Box(
+                    Modifier
+                        .testTag("tag")
+                        .semantics { contentDescription = "Test1" }
+                        .size(size)
+                        .padding(10.dp)
+                        .semantics { contentDescription = "Test2" }
+                )
+            }
+        }
+
+        val node = rule.onNodeWithTag("tag").fetchSemanticsNode()
+        val accessibilityNodeInfo = provider.createAccessibilityNodeInfo(node.id)
+
+        val rect = android.graphics.Rect()
+        accessibilityNodeInfo.getBoundsInScreen(rect)
+        val resultWidth = rect.right - rect.left
+        val resultHeight = rect.bottom - rect.top
+
+        with(rule.density) {
+            assertEquals(size.roundToPx(), resultWidth)
+            assertEquals(size.roundToPx(), resultHeight)
+        }
+    }
+
+    @Test
+    fun testReportedBounds_withOffset() {
+        val size = 100.dp
+        container.setContent {
+            Column {
+                Box(
+                    Modifier
+                        .size(size)
+                        .offset(10.dp, 10.dp)
+                        .testTag("tag")
+                        .semantics { contentDescription = "Test" }
+                )
+            }
+        }
+
+        val node = rule.onNodeWithTag("tag").fetchSemanticsNode()
+        val accessibilityNodeInfo = provider.createAccessibilityNodeInfo(node.id)
+
+        val rect = android.graphics.Rect()
+        accessibilityNodeInfo.getBoundsInScreen(rect)
+        val resultWidth = rect.right - rect.left
+        val resultHeight = rect.bottom - rect.top
+
+        with(rule.density) {
+            assertEquals(size.roundToPx(), resultWidth)
+            assertEquals(size.roundToPx(), resultHeight)
+            assertEquals(10.dp.roundToPx(), rect.left)
+            assertEquals(10.dp.roundToPx(), rect.top)
+        }
+    }
+
     private fun eventIndex(list: List<AccessibilityEvent>, event: AccessibilityEvent): Int {
         for (i in list.indices) {
             if (ReflectionEquals(list[i], null).matches(event)) {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
index e4a5a21..796d22c 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
@@ -34,6 +34,7 @@
 import androidx.compose.ui.platform.AndroidComposeViewAccessibilityDelegateCompat
 import androidx.compose.ui.platform.LocalClipboardManager
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.CustomAccessibilityAction
 import androidx.compose.ui.semantics.ProgressBarRangeInfo
 import androidx.compose.ui.semantics.ScrollAxisRange
 import androidx.compose.ui.semantics.SemanticsModifierCore
@@ -44,6 +45,7 @@
 import androidx.compose.ui.semantics.collapse
 import androidx.compose.ui.semantics.heading
 import androidx.compose.ui.semantics.copyText
+import androidx.compose.ui.semantics.customActions
 import androidx.compose.ui.semantics.cutText
 import androidx.compose.ui.semantics.disabled
 import androidx.compose.ui.semantics.stateDescription
@@ -665,6 +667,124 @@
     }
 
     @Test
+    fun sendWindowContentChangeUndefinedEventByDefault_standardActionWithTheSameLabel() {
+        val label = "label"
+        val oldSemanticsNode = createSemanticsNodeWithProperties(1, false) {
+            onClick(label = label) { true }
+        }
+        accessibilityDelegate.previousSemanticsNodes[1] =
+            AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy(
+                oldSemanticsNode,
+                mapOf()
+            )
+        val newSemanticsNode = createSemanticsNodeWithProperties(1, false) {
+            onClick(label = label) { true }
+        }
+        val newNodes = mutableMapOf<Int, SemanticsNode>()
+        newNodes[1] = newSemanticsNode
+        accessibilityDelegate.sendSemanticsPropertyChangeEvents(newNodes)
+
+        verify(container, never()).requestSendAccessibilityEvent(
+            eq(androidComposeView),
+            argThat(
+                ArgumentMatcher {
+                    it.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED &&
+                        it.contentChangeTypes == AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED
+                }
+            )
+        )
+    }
+
+    @Test
+    fun sendWindowContentChangeUndefinedEventByDefault_standardActionWithDifferentLabels() {
+        val labelOld = "labelOld"
+        val labelNew = "labelNew"
+        val oldSemanticsNode = createSemanticsNodeWithProperties(1, false) {
+            onClick(label = labelOld) { true }
+        }
+        accessibilityDelegate.previousSemanticsNodes[1] =
+            AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy(
+                oldSemanticsNode,
+                mapOf()
+            )
+        val newSemanticsNode = createSemanticsNodeWithProperties(1, false) {
+            onClick(label = labelNew) { true }
+        }
+        val newNodes = mutableMapOf<Int, SemanticsNode>()
+        newNodes[1] = newSemanticsNode
+        accessibilityDelegate.sendSemanticsPropertyChangeEvents(newNodes)
+
+        verify(container, times(1)).requestSendAccessibilityEvent(
+            eq(androidComposeView),
+            argThat(
+                ArgumentMatcher {
+                    it.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED &&
+                        it.contentChangeTypes == AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED
+                }
+            )
+        )
+    }
+
+    @Test
+    fun sendWindowContentChangeUndefinedEventByDefault_customActionWithTheSameLabel() {
+        val label = "label"
+        val oldSemanticsNode = createSemanticsNodeWithProperties(1, false) {
+            customActions = listOf(CustomAccessibilityAction(label) { true })
+        }
+        accessibilityDelegate.previousSemanticsNodes[1] =
+            AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy(
+                oldSemanticsNode,
+                mapOf()
+            )
+        val newSemanticsNode = createSemanticsNodeWithProperties(1, false) {
+            customActions = listOf(CustomAccessibilityAction(label) { true })
+        }
+        val newNodes = mutableMapOf<Int, SemanticsNode>()
+        newNodes[1] = newSemanticsNode
+        accessibilityDelegate.sendSemanticsPropertyChangeEvents(newNodes)
+
+        verify(container, never()).requestSendAccessibilityEvent(
+            eq(androidComposeView),
+            argThat(
+                ArgumentMatcher {
+                    it.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED &&
+                        it.contentChangeTypes == AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED
+                }
+            )
+        )
+    }
+
+    @Test
+    fun sendWindowContentChangeUndefinedEventByDefault_customActionWithDifferentLabels() {
+        val labelOld = "labelOld"
+        val labelNew = "labelNew"
+        val oldSemanticsNode = createSemanticsNodeWithProperties(1, false) {
+            customActions = listOf(CustomAccessibilityAction(labelOld) { true })
+        }
+        accessibilityDelegate.previousSemanticsNodes[1] =
+            AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy(
+                oldSemanticsNode,
+                mapOf()
+            )
+        val newSemanticsNode = createSemanticsNodeWithProperties(1, false) {
+            customActions = listOf(CustomAccessibilityAction(labelNew) { true })
+        }
+        val newNodes = mutableMapOf<Int, SemanticsNode>()
+        newNodes[1] = newSemanticsNode
+        accessibilityDelegate.sendSemanticsPropertyChangeEvents(newNodes)
+
+        verify(container, times(1)).requestSendAccessibilityEvent(
+            eq(androidComposeView),
+            argThat(
+                ArgumentMatcher {
+                    it.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED &&
+                        it.contentChangeTypes == AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED
+                }
+            )
+        )
+    }
+
+    @Test
     fun testCollectionItemInfo() {
         rule.setContent {
             Column(Modifier.selectableGroup()) {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawModifierTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawModifierTest.kt
index dbd0c19..2c811c9 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawModifierTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawModifierTest.kt
@@ -50,6 +50,7 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.LargeTest
 import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth.assertThat
@@ -275,6 +276,7 @@
         }
     }
 
+    @FlakyTest(bugId = 182512695)
     @Test
     fun testCacheInvalidatedAfterLayoutDirectionChange() {
         var cacheBuildCount = 0
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/GraphicsLayerTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/GraphicsLayerTest.kt
index 2c628c4..9ced826 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/GraphicsLayerTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/GraphicsLayerTest.kt
@@ -17,7 +17,9 @@
 package androidx.compose.ui.draw
 
 import android.os.Build
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.runtime.getValue
@@ -33,12 +35,14 @@
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.GraphicsLayerScope
 import androidx.compose.ui.graphics.Outline
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.TransformOrigin
 import androidx.compose.ui.graphics.asAndroidBitmap
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.boundsInRoot
 import androidx.compose.ui.layout.boundsInWindow
@@ -50,9 +54,12 @@
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.TestActivity
 import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.click
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performGesture
+import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
@@ -60,6 +67,8 @@
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -478,4 +487,49 @@
             }
         }
     }
+
+    @Test
+    fun testClickOnScaledElement() {
+        var firstClicked = false
+        var secondClicked = false
+        rule.setContent {
+            Layout(
+                content = {
+                    Box(
+                        Modifier.fillMaxSize().clickable {
+                            firstClicked = true
+                        }
+                    )
+                    Box(
+                        Modifier.fillMaxSize().clickable {
+                            secondClicked = true
+                        }
+                    )
+                },
+                modifier = Modifier.testTag("layout")
+            ) { measurables, _ ->
+                val itemConstraints = Constraints.fixed(100, 100)
+                val first = measurables[0].measure(itemConstraints)
+                val second = measurables[1].measure(itemConstraints)
+                layout(100, 200) {
+                    val layer: GraphicsLayerScope.() -> Unit = {
+                        scaleX = 0.5f
+                        scaleY = 0.5f
+                    }
+                    first.placeWithLayer(0, 0, layerBlock = layer)
+                    second.placeWithLayer(0, 100, layerBlock = layer)
+                }
+            }
+        }
+
+        rule.onNodeWithTag("layout")
+            .performGesture {
+                click(position = Offset(50f, 170f))
+            }
+
+        rule.runOnIdle {
+            assertFalse("First element is clicked", firstClicked)
+            assertTrue("Second element is not clicked", secondClicked)
+        }
+    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt
index 59a95ba..a1b44c7 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/autofill/AndroidAutofill.android.kt
@@ -28,6 +28,7 @@
 import androidx.annotation.RequiresApi
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.graphics.toAndroidRect
+import androidx.compose.ui.util.fastMap
 
 /**
  * Autofill implementation for Android.
@@ -85,7 +86,7 @@
             AutofillApi26Helper.setAutofillType(child, View.AUTOFILL_TYPE_TEXT)
             AutofillApi26Helper.setAutofillHints(
                 child,
-                autofillNode.autofillTypes.map { it.androidType }.toTypedArray()
+                autofillNode.autofillTypes.fastMap { it.androidType }.toTypedArray()
             )
 
             if (autofillNode.boundingBox == null) {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilter.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilter.android.kt
index 157fd50..f60d11f 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilter.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/input/pointer/PointerInteropFilter.android.kt
@@ -27,6 +27,7 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.util.fastAll
 import androidx.compose.ui.util.fastAny
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.viewinterop.AndroidViewHolder
@@ -204,7 +205,7 @@
                 if (pass == PointerEventPass.Final) {
                     // If all of the changes were up changes, then the "event stream" has ended
                     // and we reset.
-                    if (changes.all { it.changedToUpIgnoreConsumed() }) {
+                    if (changes.fastAll { it.changedToUpIgnoreConsumed() }) {
                         reset()
                     }
                 }
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AccessibilityIterators.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AccessibilityIterators.android.kt
index 98c0c39..b33cc3f 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AccessibilityIterators.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AccessibilityIterators.android.kt
@@ -23,6 +23,7 @@
 import androidx.compose.ui.text.style.ResolvedTextDirection
 import java.text.BreakIterator
 import java.util.Locale
+import kotlin.math.roundToInt
 
 /**
  * This class contains the implementation of text segment iterators
@@ -454,13 +455,9 @@
             if (current >= text.length) {
                 return null
             }
+            val pageHeight: Int
             try {
-                tempRect = Rect(
-                    node.boundsInWindow.left.toInt(),
-                    node.boundsInWindow.top.toInt(),
-                    node.boundsInWindow.right.toInt(),
-                    node.boundsInWindow.bottom.toInt()
-                )
+                pageHeight = node.boundsInRoot.height.roundToInt()
                 // TODO(b/153198816): check whether we still get this exception when R is in.
             } catch (e: IllegalStateException) {
                 return null
@@ -473,7 +470,6 @@
             // TODO: Please help me translate the below where mView is the TextView
             //  final int pageHeight = mTempRect.height() - mView.getTotalPaddingTop()
             //                    - mView.getTotalPaddingBottom();
-            val pageHeight = tempRect.height()
             val nextPageStartY = currentLineTop + pageHeight
             val lastLineTop = layoutResult.getLineTop(layoutResult.lineCount - 1)
             val currentPageEndLine = if (nextPageStartY < lastLineTop)
@@ -493,13 +489,9 @@
             if (current <= 0) {
                 return null
             }
+            val pageHeight: Int
             try {
-                tempRect = Rect(
-                    node.boundsInWindow.left.toInt(),
-                    node.boundsInWindow.top.toInt(),
-                    node.boundsInWindow.right.toInt(),
-                    node.boundsInWindow.bottom.toInt()
-                )
+                pageHeight = node.boundsInRoot.height.roundToInt()
                 // TODO(b/153198816): check whether we still get this exception when R is in.
             } catch (e: IllegalStateException) {
                 return null
@@ -513,7 +505,6 @@
             //  Please help me translate the below where mView is the TextView
             //  final int pageHeight = mTempRect.height() - mView.getTotalPaddingTop()
             //                    - mView.getTotalPaddingBottom();
-            val pageHeight = tempRect.height()
             val previousPageEndY = currentLineTop - pageHeight
             var currentPageStartLine = if (previousPageEndY > 0)
                 layoutResult.getLineForVerticalPosition(previousPageEndY) else 0
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
index a7ebb9e..c71cc72 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
@@ -57,7 +57,11 @@
 import androidx.compose.ui.text.platform.toAccessibilitySpannableString
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.fastJoinToString
+import androidx.compose.ui.fastReduce
+import androidx.compose.ui.fastZipWithNext
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.semantics.AccessibilityAction
 import androidx.compose.ui.semantics.ProgressBarRangeInfo
 import androidx.compose.ui.text.InternalTextApi
 import androidx.compose.ui.util.fastForEachIndexed
@@ -69,6 +73,8 @@
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.delay
 import kotlin.math.abs
+import kotlin.math.ceil
+import kotlin.math.floor
 
 private fun LayoutNode.findClosestParentNode(selector: (LayoutNode) -> Boolean): LayoutNode? {
     var currentParent = this.parent
@@ -274,10 +280,10 @@
             val bottomRightInScreen = view.localToScreen(boundsInRoot.bottomRight)
             info.setBoundsInScreen(
                 android.graphics.Rect(
-                    topLeftInScreen.x.toInt(),
-                    topLeftInScreen.y.toInt(),
-                    bottomRightInScreen.x.toInt(),
-                    bottomRightInScreen.y.toInt()
+                    floor(topLeftInScreen.x).toInt(),
+                    floor(topLeftInScreen.y).toInt(),
+                    ceil(bottomRightInScreen.x).toInt(),
+                    ceil(bottomRightInScreen.y).toInt()
                 )
             )
         } catch (e: IllegalStateException) {
@@ -286,7 +292,7 @@
             info.setBoundsInScreen(android.graphics.Rect())
         }
 
-        for (child in semanticsNode.children) {
+        semanticsNode.children.fastForEach { child ->
             if (currentSemanticsNodes.contains(child.id)) {
                 info.addChild(view, child.id)
             }
@@ -310,8 +316,11 @@
         setText(semanticsNode, info)
         info.stateDescription =
             semanticsNode.config.getOrNull(SemanticsProperties.StateDescription)
-        info.contentDescription =
-            semanticsNode.config.getOrNull(SemanticsProperties.ContentDescription)
+
+        // If the node has a content description (in unmerged config), it will be used. Otherwise
+        // for merging node we concatenate content descriptions and texts from its children.
+        info.contentDescription = calculateContentDescription(semanticsNode)
+
         semanticsNode.config.getOrNull(SemanticsProperties.Heading)?.let {
             info.isHeading = true
         }
@@ -420,7 +429,10 @@
                 AccessibilityNodeInfoCompat.MOVEMENT_GRANULARITY_WORD or
                 AccessibilityNodeInfoCompat.MOVEMENT_GRANULARITY_PARAGRAPH
             // We only traverse the text when contentDescription is not set.
-            if (info.contentDescription.isNullOrEmpty() &&
+            val contentDescription = semanticsNode.unmergedConfig.getOrNull(
+                SemanticsProperties.ContentDescription
+            )
+            if (contentDescription.isNullOrEmpty() &&
                 semanticsNode.config.contains(SemanticsActions.GetTextLayoutResult)
             ) {
                 info.movementGranularities = info.movementGranularities or
@@ -609,7 +621,7 @@
                     val oldLabelToActionId = labelToActionId[virtualViewId]
                     val availableIds = AccessibilityActionsResourceIds.toMutableList()
                     val unassignedActions = mutableListOf<CustomAccessibilityAction>()
-                    for (action in customActions) {
+                    customActions.fastForEach { action ->
                         if (oldLabelToActionId!!.contains(action.label)) {
                             val actionId = oldLabelToActionId[action.label]
                             currentActionIdToLabel.put(actionId!!, action.label)
@@ -624,7 +636,7 @@
                             unassignedActions.add(action)
                         }
                     }
-                    for ((index, action) in unassignedActions.withIndex()) {
+                    unassignedActions.fastForEachIndexed { index, action ->
                         val actionId = availableIds[index]
                         currentActionIdToLabel.put(actionId, action.label)
                         currentLabelToActionId[action.label] = actionId
@@ -635,7 +647,7 @@
                         )
                     }
                 } else {
-                    for ((index, action) in customActions.withIndex()) {
+                    customActions.fastForEachIndexed { index, action ->
                         val actionId = AccessibilityActionsResourceIds[index]
                         currentActionIdToLabel.put(actionId, action.label)
                         currentLabelToActionId[action.label] = actionId
@@ -949,8 +961,10 @@
                         ) &&
                         xScrollState.value() < xScrollState.maxValue()
                     ) {
+                        // here and below innerLayoutNodeWrapper is used to calculate the width
+                        // and height to exclude the paddings
                         return scrollAction.action?.invoke(
-                            node.boundsInWindow.right - node.boundsInWindow.left,
+                            node.layoutNode.coordinates.size.width.toFloat(),
                             0f
                         ) ?: false
                     }
@@ -968,7 +982,7 @@
                         xScrollState.value() > 0
                     ) {
                         return scrollAction.action?.invoke(
-                            -(node.boundsInWindow.right - node.boundsInWindow.left),
+                            -node.layoutNode.coordinates.size.width.toFloat(),
                             0f
                         ) ?: false
                     }
@@ -991,7 +1005,7 @@
                     ) {
                         return scrollAction.action?.invoke(
                             0f,
-                            node.boundsInWindow.bottom - node.boundsInWindow.top
+                            node.layoutNode.coordinates.size.height.toFloat()
                         ) ?: false
                     }
                     if ((
@@ -1009,7 +1023,7 @@
                     ) {
                         return scrollAction.action?.invoke(
                             0f,
-                            -(node.boundsInWindow.bottom - node.boundsInWindow.top)
+                            -node.layoutNode.coordinates.size.height.toFloat()
                         ) ?: false
                     }
                 }
@@ -1052,7 +1066,7 @@
             else -> {
                 val label = actionIdToLabel[virtualViewId]?.get(action) ?: return false
                 val customActions = node.config.getOrNull(CustomActions) ?: return false
-                for (customAction in customActions) {
+                customActions.fastForEach { customAction ->
                     if (customAction.label == label) {
                         return customAction.action()
                     }
@@ -1111,37 +1125,39 @@
                     continue
                 }
                 val bounds = textLayoutResult.getBoundingBox(positionInfoStartIndex + i)
-                val screenBounds: Rect?
-                // Only the visible/partial visible locations are used.
-                if (textNode != null) {
-                    screenBounds = toScreenCoords(textNode, bounds)
-                } else {
-                    screenBounds = bounds
-                }
-                if (screenBounds == null) {
-                    boundingRects.add(null)
-                } else {
-                    boundingRects.add(
-                        RectF(
-                            screenBounds.left,
-                            screenBounds.top,
-                            screenBounds.right,
-                            screenBounds.bottom
-                        )
-                    )
-                }
+                val boundsOnScreen = toScreenCoords(textNode, bounds)
+                boundingRects.add(boundsOnScreen)
             }
             info.extras.putParcelableArray(extraDataKey, boundingRects.toTypedArray())
         }
     }
 
-    private fun toScreenCoords(textNode: SemanticsNode, bounds: Rect): Rect? {
-        val screenBounds = bounds.translate(textNode.positionInWindow)
-        val globalBounds = textNode.boundsInWindow
-        if (screenBounds.overlaps(globalBounds)) {
-            return screenBounds.intersect(globalBounds)
+    private fun toScreenCoords(textNode: SemanticsNode?, bounds: Rect): RectF? {
+        if (textNode == null) return null
+        val boundsInRoot = bounds.translate(textNode.positionInRoot)
+        val textNodeBoundsInRoot = textNode.boundsInRoot
+
+        // Only visible or partially visible locations are used.
+        val visibleBounds = if (boundsInRoot.overlaps(textNodeBoundsInRoot)) {
+            boundsInRoot.intersect(textNodeBoundsInRoot)
+        } else {
+            null
         }
-        return null
+
+        return if (visibleBounds != null) {
+            val topLeftInScreen =
+                view.localToScreen(Offset(visibleBounds.left, visibleBounds.top))
+            val bottomRightInScreen =
+                view.localToScreen(Offset(visibleBounds.right, visibleBounds.bottom))
+            RectF(
+                topLeftInScreen.x,
+                topLeftInScreen.y,
+                bottomRightInScreen.x,
+                bottomRightInScreen.y
+            )
+        } else {
+            null
+        }
     }
 
     // TODO: this only works for single text/text field.
@@ -1601,10 +1617,34 @@
                             AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED
                         )
                     }
+                    CustomActions -> {
+                        val actions = newNode.config[CustomActions]
+                        val oldActions = oldNode.config.getOrNull(CustomActions)
+                        if (oldActions != null) {
+                            // Suppose actions with the same label should be deduped.
+                            val labels = mutableSetOf<String>()
+                            actions.fastForEach { action ->
+                                labels.add(action.label)
+                            }
+                            val oldLabels = mutableSetOf<String>()
+                            oldActions.fastForEach { action ->
+                                oldLabels.add(action.label)
+                            }
+                            propertyChanged =
+                                !(labels.containsAll(oldLabels) && oldLabels.containsAll(labels))
+                        } else if (actions.isNotEmpty()) {
+                            propertyChanged = true
+                        }
+                    }
                     // TODO(b/151840490) send the correct events for certain properties, like view
                     //  selected.
                     else -> {
-                        propertyChanged = true
+                        if (entry.value is AccessibilityAction<*>) {
+                            propertyChanged = !(entry.value as AccessibilityAction<*>)
+                                .accessibilityEquals(oldNode.config.getOrNull(entry.key))
+                        } else {
+                            propertyChanged = true
+                        }
                     }
                 }
             }
@@ -1686,6 +1726,7 @@
         extendSelection: Boolean
     ): Boolean {
         val text = getIterableTextForAccessibility(node)
+            ?: calculateContentDescriptionFromChildren(node)
         if (text.isNullOrEmpty()) {
             return false
         }
@@ -1734,7 +1775,9 @@
         event.toIndex = toIndex
         event.action = action
         event.movementGranularity = granularity
-        event.text.add(getIterableTextForAccessibility(node))
+        event.text.add(
+            getIterableTextForAccessibility(node) ?: calculateContentDescriptionFromChildren(node)
+        )
         sendEvent(event)
     }
 
@@ -1776,7 +1819,7 @@
 
     private fun getAccessibilitySelectionStart(node: SemanticsNode): Int {
         // If there is ContentDescription, it will be used instead of text during traversal.
-        if (!node.config.contains(SemanticsProperties.ContentDescription) &&
+        if (!node.unmergedConfig.contains(SemanticsProperties.ContentDescription) &&
             node.config.contains(SemanticsProperties.TextSelectionRange)
         ) {
             return node.config[SemanticsProperties.TextSelectionRange].start
@@ -1786,7 +1829,7 @@
 
     private fun getAccessibilitySelectionEnd(node: SemanticsNode): Int {
         // If there is ContentDescription, it will be used instead of text during traversal.
-        if (!node.config.contains(SemanticsProperties.ContentDescription) &&
+        if (!node.unmergedConfig.contains(SemanticsProperties.ContentDescription) &&
             node.config.contains(SemanticsProperties.TextSelectionRange)
         ) {
             return node.config[SemanticsProperties.TextSelectionRange].end
@@ -1804,7 +1847,10 @@
         node: SemanticsNode?,
         granularity: Int
     ): AccessibilityIterators.TextSegmentIterator? {
+        if (node == null) return null
+
         val text = getIterableTextForAccessibility(node)
+            ?: calculateContentDescriptionFromChildren(node)
         if (text.isNullOrEmpty()) {
             return null
         }
@@ -1831,7 +1877,7 @@
             AccessibilityNodeInfoCompat.MOVEMENT_GRANULARITY_LINE,
             AccessibilityNodeInfoCompat.MOVEMENT_GRANULARITY_PAGE -> {
                 // Line and page granularity are only for static text or text field.
-                if (node == null || !node.config.contains(SemanticsActions.GetTextLayoutResult)) {
+                if (!node.config.contains(SemanticsActions.GetTextLayoutResult)) {
                     return null
                 }
                 // TODO(b/157474582): Note now it only works for single Text/TextField until we
@@ -1860,9 +1906,11 @@
     }
 
     /**
-     * Gets the text reported for accessibility purposes.
+     * Gets the text reported for accessibility purposes. If a text node has a content description
+     * in the unmerged config, it will be used instead of the text.
      *
-     * @return The accessibility text.
+     * This function is basically prioritising the content description over the text or editable
+     * text of the text and text field nodes.
      */
     private fun getIterableTextForAccessibility(node: SemanticsNode?): String? {
         if (node == null) {
@@ -1870,8 +1918,8 @@
         }
         // Note in android framework, TextView set this to its text. This is changed to
         // prioritize content description, even for Text.
-        if (node.config.contains(SemanticsProperties.ContentDescription)) {
-            return node.config[SemanticsProperties.ContentDescription]
+        if (node.unmergedConfig.contains(SemanticsProperties.ContentDescription)) {
+            return node.unmergedConfig[SemanticsProperties.ContentDescription]
         }
 
         if (node.isTextField) {
@@ -1896,6 +1944,85 @@
         }
     }
 
+    /**
+     * Content description of the node that itself has a content description will be reported as
+     * is. Text node and text field node without a content description ignore it and report null.
+     * In other situations we concatenate non-merging children's content description or texts
+     * using [calculateContentDescriptionFromChildren]. Note that we ignore merging children as
+     * they should be focused separately.
+     *
+     * This method is used to set the content description of the node.
+     */
+    private fun calculateContentDescription(node: SemanticsNode): String? {
+        val contentDescription =
+            node.unmergedConfig.getOrNull(SemanticsProperties.ContentDescription)
+        if (!contentDescription.isNullOrEmpty()) {
+            return contentDescription
+        }
+
+        if (node.unmergedConfig.contains(SemanticsProperties.Text) ||
+            node.unmergedConfig.contains(SemanticsActions.SetText)
+        ) {
+            return null
+        }
+
+        // if node merges its children, concatenate their content descriptions and texts
+        return calculateContentDescriptionFromChildren(node)
+    }
+
+    /**
+     * Concatenate content descriptions and texts of non-merging children of the [node] that
+     * merges its children.
+     */
+    fun calculateContentDescriptionFromChildren(node: SemanticsNode): String? {
+        fun concatenateChildrenContentDescriptionAndText(node: SemanticsNode): List<String> {
+            val childDescriptions = mutableListOf<String>()
+
+            node.unmergedChildren().fastForEach { childNode ->
+                // Don't merge child that merges its children because that child node will be focused
+                // separately
+                if (childNode.unmergedConfig.isMergingSemanticsOfDescendants) {
+                    return@fastForEach
+                }
+
+                val contentDescription =
+                    childNode.unmergedConfig.getOrNull(SemanticsProperties.ContentDescription)
+                if (!contentDescription.isNullOrEmpty()) {
+                    childDescriptions.add(contentDescription)
+                    return@fastForEach
+                }
+
+                // check if it's a text field node
+                if (childNode.config.contains(SemanticsActions.SetText)) {
+                    val text = getTextForTextField(childNode)
+                    if (!text.isNullOrEmpty()) {
+                        childDescriptions.add(text)
+                    }
+                    return@fastForEach
+                }
+
+                // check if it's a text node
+                val text = childNode.unmergedConfig.getOrNull(SemanticsProperties.Text)
+                if (!text.isNullOrEmpty()) {
+                    childDescriptions.add(text.text)
+                    return@fastForEach
+                }
+
+                concatenateChildrenContentDescriptionAndText(childNode).fastForEach {
+                    childDescriptions.add(it)
+                }
+            }
+
+            return childDescriptions
+        }
+
+        // if node merges its children, concatenate their content descriptions and texts
+        if (node.unmergedConfig.isMergingSemanticsOfDescendants) {
+            return concatenateChildrenContentDescriptionAndText(node).fastJoinToString()
+        }
+        return null
+    }
+
     private fun setCollectionInfo(node: SemanticsNode, info: AccessibilityNodeInfoCompat) {
         val groupedChildren = mutableListOf<SemanticsNode>()
 
@@ -1967,7 +2094,7 @@
     private fun calculateIfHorizontallyStacked(items: List<SemanticsNode>): Boolean {
         if (items.count() < 2) return true
 
-        val deltas = items.zipWithNext { el1, el2 ->
+        val deltas = items.fastZipWithNext { el1, el2 ->
             Offset(
                 abs(el1.boundsInRoot.center.x - el2.boundsInRoot.center.x),
                 abs(el1.boundsInRoot.center.y - el2.boundsInRoot.center.y)
@@ -1975,7 +2102,7 @@
         }
         val (deltaX, deltaY) = when (deltas.count()) {
             1 -> deltas.first()
-            else -> deltas.reduce { result, element -> result + element }
+            else -> deltas.fastReduce { result, element -> result + element }
         }
         return deltaY < deltaX
     }
@@ -2068,6 +2195,17 @@
 private val SemanticsNode.isPassword: Boolean get() = config.contains(SemanticsProperties.Password)
 private val SemanticsNode.isTextField get() = this.config.contains(SemanticsActions.SetText)
 
+private fun AccessibilityAction<*>.accessibilityEquals(other: Any?): Boolean {
+    if (this === other) return true
+    if (other !is AccessibilityAction<*>) return false
+
+    if (label != other.label) return false
+    if (action == null && other.action != null) return false
+    if (action != null && other.action == null) return false
+
+    return true
+}
+
 /**
  * Finds pruned [SemanticsNode]s in the tree owned by this [SemanticsOwner]. A semantics node
  * completely covered by siblings drawn on top of it will be pruned. Return the results in a
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeView.android.kt
index a2c74d0..7d990253 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ComposeView.android.kt
@@ -31,6 +31,13 @@
 import androidx.lifecycle.ViewTreeLifecycleOwner
 import androidx.savedstate.ViewTreeSavedStateRegistryOwner
 
+private const val MissingViewTreeDependenciesMessage =
+    "If you are adding this ComposeView to an AppCompatActivity, make sure you " +
+        "are using AppCompat version 1.3+. If you are adding this ComposeView to a " +
+        "Fragment, make sure you are using Fragment version 1.3+. For other cases, manually " +
+        "set owners on this view by using `ViewTreeLifecycleOwner.set()` and " +
+        "`ViewTreeSavedStateRegistryOwner.set()`."
+
 /**
  * Base class for custom [android.view.View]s implemented using Jetpack Compose UI.
  * Subclasses should implement the [Content] function with the appropriate content.
@@ -168,9 +175,25 @@
         }
     }
 
+    private fun checkViewTreeOwners() {
+        checkNotNull(ViewTreeLifecycleOwner.get(this)) {
+            "ViewTreeLifecycleOwner not set for this ComposeView. " +
+                MissingViewTreeDependenciesMessage
+        }
+        checkNotNull(ViewTreeSavedStateRegistryOwner.get(this)) {
+            "ViewTreeSavedStateRegistryOwner not set for this ComposeView. " +
+                MissingViewTreeDependenciesMessage
+        }
+        // Not checking for ViewTreeViewModelStoreOwner as we don't need it inside Compose,
+        // but we provide it in ComponentActivity.setContent for convenience.
+    }
+
     @Suppress("DEPRECATION") // Still using ViewGroup.setContent for now
     private fun ensureCompositionCreated() {
         if (composition == null) {
+            if (isAttachedToWindow) {
+                checkViewTreeOwners()
+            }
             try {
                 creatingComposition = true
                 composition = setContent(
@@ -204,19 +227,9 @@
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
 
-        val message = "If you are adding this ComposeView to an AppCompatActivity, make sure you " +
-            "are using AppCompat version 1.3+. If you are adding this ComposeView to a " +
-            "Fragment, make sure you are using Fragment version 1.3+. For other cases, manually " +
-            "set owners on this view by using `ViewTreeLifecycleOwner.set()` and " +
-            "`ViewTreeSavedStateRegistryOwner.set()`."
-        checkNotNull(ViewTreeLifecycleOwner.get(this)) {
-            "ViewTreeLifecycleOwner not set for this ComposeView. $message"
+        if (composition != null) {
+            checkViewTreeOwners()
         }
-        checkNotNull(ViewTreeSavedStateRegistryOwner.get(this)) {
-            "ViewTreeSavedStateRegistryOwner not set for this ComposeView. $message"
-        }
-        // Not checking for ViewTreeViewModelStoreOwner as we don't need it inside Compose, but we
-        // provide it in ComponentActivity.setContent for convenience.
 
         if (shouldCreateCompositionOnAttachedToWindow) {
             ensureCompositionCreated()
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/DisposableSaveableStateRegistry.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/DisposableSaveableStateRegistry.android.kt
index c45c169..04c94f8 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/DisposableSaveableStateRegistry.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/DisposableSaveableStateRegistry.android.kt
@@ -18,16 +18,13 @@
 
 package androidx.compose.ui.platform
 
-import android.annotation.SuppressLint
 import android.os.Binder
 import android.os.Bundle
-import android.os.Parcel
 import android.os.Parcelable
 import android.util.Size
 import android.util.SizeF
 import android.util.SparseArray
 import android.view.View
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.neverEqualPolicy
 import androidx.compose.runtime.referentialEqualityPolicy
 import androidx.compose.runtime.saveable.SaveableStateRegistry
@@ -117,11 +114,7 @@
  * Checks that [value] can be stored inside [Bundle].
  */
 private fun canBeSavedToBundle(value: Any): Boolean {
-    for (cl in AcceptableClasses) {
-        if (cl.isInstance(value)) {
-            return true
-        }
-    }
+    // SnapshotMutableStateImpl is Parcelable, but we do extra checks
     if (value is SnapshotMutableState<*>) {
         if (value.policy === neverEqualPolicy<Any?>() ||
             value.policy === structuralEqualityPolicy<Any?>() ||
@@ -129,6 +122,13 @@
         ) {
             val stateValue = value.value
             return if (stateValue == null) true else canBeSavedToBundle(stateValue)
+        } else {
+            return false
+        }
+    }
+    for (cl in AcceptableClasses) {
+        if (cl.isInstance(value)) {
+            return true
         }
     }
     return false
@@ -164,7 +164,6 @@
     val map = mutableMapOf<String, List<Any?>>()
     this.keySet().forEach { key ->
         val list = getParcelableArrayList<Parcelable?>(key) as ArrayList<Any?>
-        unwrapMutableStatesIn(list)
         map[key] = list
     }
     return map
@@ -174,7 +173,6 @@
     val bundle = Bundle()
     forEach { (key, list) ->
         val arrayList = if (list is ArrayList<Any?>) list else ArrayList(list)
-        wrapMutableStatesIn(arrayList)
         bundle.putParcelableArrayList(
             key,
             arrayList as ArrayList<Parcelable?>
@@ -182,142 +180,3 @@
     }
     return bundle
 }
-
-private fun wrapMutableStatesIn(list: MutableList<Any?>) {
-    list.forEachIndexed { index, value ->
-        if (value is SnapshotMutableState<*>) {
-            list[index] = ParcelableMutableStateHolder(value)
-        } else {
-            wrapMutableStatesInListOrMap(value)
-        }
-    }
-}
-
-private fun wrapMutableStatesIn(map: MutableMap<Any?, Any?>) {
-    map.forEach { (key, value) ->
-        if (value is SnapshotMutableState<*>) {
-            map[key] = ParcelableMutableStateHolder(value)
-        } else {
-            wrapMutableStatesInListOrMap(value)
-        }
-    }
-}
-
-private fun wrapMutableStatesInListOrMap(value: Any?) {
-    when (value) {
-        is MutableList<*> -> {
-            wrapMutableStatesIn(value as MutableList<Any?>)
-        }
-        is List<*> -> {
-            value.forEach {
-                check(it !is SnapshotMutableState<*>) {
-                    "Unexpected immutable list containing MutableState!"
-                }
-            }
-        }
-        is MutableMap<*, *> -> {
-            wrapMutableStatesIn(value as MutableMap<Any?, Any?>)
-        }
-        is Map<*, *> -> {
-            value.forEach {
-                check(it.value !is SnapshotMutableState<*>) {
-                    "Unexpected immutable map containing MutableState!"
-                }
-            }
-        }
-    }
-}
-
-private fun unwrapMutableStatesIn(list: MutableList<Any?>) {
-    list.forEachIndexed { index, value ->
-        if (value is ParcelableMutableStateHolder) {
-            list[index] = value.state
-        } else {
-            unwrapMutableStatesInListOrMap(value)
-        }
-    }
-}
-
-private fun unwrapMutableStatesIn(map: MutableMap<Any?, Any?>) {
-    map.forEach { (key, value) ->
-        if (value is ParcelableMutableStateHolder) {
-            map[key] = value.state
-        } else {
-            unwrapMutableStatesInListOrMap(value)
-        }
-    }
-}
-
-private fun unwrapMutableStatesInListOrMap(value: Any?) {
-    when (value) {
-        is MutableList<*> -> {
-            unwrapMutableStatesIn(value as MutableList<Any?>)
-        }
-        is MutableMap<*, *> -> {
-            unwrapMutableStatesIn(value as MutableMap<Any?, Any?>)
-        }
-    }
-}
-
-@SuppressLint("BanParcelableUsage")
-private class ParcelableMutableStateHolder : Parcelable {
-
-    val state: SnapshotMutableState<*>
-
-    constructor(state: SnapshotMutableState<*>) {
-        this.state = state
-    }
-
-    private constructor(parcel: Parcel, loader: ClassLoader?) {
-        val value = parcel.readValue(loader ?: javaClass.classLoader)
-        val policyIndex = parcel.readInt()
-        state = mutableStateOf(
-            value,
-            when (policyIndex) {
-                PolicyNeverEquals -> neverEqualPolicy()
-                PolicyStructuralEquality -> structuralEqualityPolicy()
-                PolicyReferentialEquality -> referentialEqualityPolicy()
-                else -> throw IllegalStateException(
-                    "Restored an incorrect MutableState policy $policyIndex"
-                )
-            }
-        ) as SnapshotMutableState
-    }
-
-    override fun writeToParcel(parcel: Parcel, flags: Int) {
-        parcel.writeValue(state.value)
-        parcel.writeInt(
-            when (state.policy) {
-                neverEqualPolicy<Any?>() -> PolicyNeverEquals
-                structuralEqualityPolicy<Any?>() -> PolicyStructuralEquality
-                referentialEqualityPolicy<Any?>() -> PolicyReferentialEquality
-                else -> throw IllegalStateException(
-                    "Only known types of MutableState's SnapshotMutationPolicy are supported"
-                )
-            }
-        )
-    }
-
-    override fun describeContents(): Int {
-        return 0
-    }
-
-    companion object {
-        private const val PolicyNeverEquals = 0
-        private const val PolicyStructuralEquality = 1
-        private const val PolicyReferentialEquality = 2
-
-        @Suppress("unused")
-        @JvmField
-        val CREATOR: Parcelable.Creator<ParcelableMutableStateHolder> =
-            object : Parcelable.ClassLoaderCreator<ParcelableMutableStateHolder> {
-                override fun createFromParcel(parcel: Parcel, loader: ClassLoader) =
-                    ParcelableMutableStateHolder(parcel, loader)
-
-                override fun createFromParcel(parcel: Parcel) =
-                    ParcelableMutableStateHolder(parcel, null)
-
-                override fun newArray(size: Int) = arrayOfNulls<ParcelableMutableStateHolder?>(size)
-            }
-    }
-}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/RecordingInputConnection.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/RecordingInputConnection.android.kt
index 60d8f5f..03e6692 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/RecordingInputConnection.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/text/input/RecordingInputConnection.android.kt
@@ -135,7 +135,7 @@
         if (DEBUG) { Log.d(TAG, "endBatchEdit()") }
         batchDepth--
         if (batchDepth == 0 && editCommands.isNotEmpty()) {
-            eventCallback.onEditCommands(editCommands.toList())
+            eventCallback.onEditCommands(editCommands.toMutableList())
             editCommands.clear()
         }
         return batchDepth > 0
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
index 9bcc554..f63254f 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidPopup.android.kt
@@ -60,6 +60,7 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastMap
 import androidx.lifecycle.ViewTreeLifecycleOwner
 import androidx.lifecycle.ViewTreeViewModelStoreOwner
 import androidx.savedstate.ViewTreeSavedStateRegistryOwner
@@ -278,7 +279,7 @@
                 }
             }
             else -> {
-                val placeables = measurables.map { it.measure(constraints) }
+                val placeables = measurables.fastMap { it.measure(constraints) }
                 var width = 0
                 var height = 0
                 for (i in 0..placeables.lastIndex) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/TempListUtils.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/TempListUtils.kt
new file mode 100644
index 0000000..940317c
--- /dev/null
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/TempListUtils.kt
@@ -0,0 +1,179 @@
+/*
+ * 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.ui
+
+import androidx.compose.ui.util.fastForEach
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+
+// TODO: remove these when we can add new APIs to ui-util outside of beta cycle
+
+/**
+ * Returns a list containing the results of applying the given [transform] function
+ * to each pair of two adjacent elements in this collection.
+ *
+ * The returned list is empty if this collection contains less than two elements.
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T, R> List<T>.fastZipWithNext(transform: (T, T) -> R): List<R> {
+    contract { callsInPlace(transform) }
+    if (size == 0 || size == 1) return emptyList()
+    val result = mutableListOf<R>()
+    var current = get(0)
+    // `until` as we don't want to invoke this for the last element, since that won't have a `next`
+    for (i in 0 until lastIndex) {
+        val next = get(i + 1)
+        result.add(transform(current, next))
+        current = next
+    }
+    return result
+}
+
+/**
+ * Accumulates value starting with the first element and applying [operation] from left to right
+ * to current accumulator value and each element.
+ *
+ * Throws an exception if this collection is empty. If the collection can be empty in an expected
+ * way, please use [reduceOrNull] instead. It returns `null` when its receiver is empty.
+ *
+ * @param [operation] function that takes current accumulator value and an element,
+ * and calculates the next accumulator value.
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <S, T : S> List<T>.fastReduce(operation: (acc: S, T) -> S): S {
+    contract { callsInPlace(operation) }
+    if (isEmpty()) throw UnsupportedOperationException("Empty collection can't be reduced.")
+    var accumulator: S = first()
+    for (i in 1..lastIndex) {
+        accumulator = operation(accumulator, get(i))
+    }
+    return accumulator
+}
+
+/**
+ * Returns a [Map] containing key-value pairs provided by [transform] function
+ * applied to elements of the given collection.
+ *
+ * If any of two pairs would have the same key the last one gets added to the map.
+ *
+ * The returned map preserves the entry iteration order of the original collection.
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T, K, V> List<T>.fastAssociate(transform: (T) -> Pair<K, V>): Map<K, V> {
+    contract { callsInPlace(transform) }
+    val target = LinkedHashMap<K, V>(size)
+    fastForEach { e ->
+        target += transform(e)
+    }
+    return target
+}
+
+/**
+ * Returns a list of values built from the elements of `this` collection and the [other] collection with the same index
+ * using the provided [transform] function applied to each pair of elements.
+ * The returned list has length of the shortest collection.
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T, R, V> List<T>.fastZip(
+    other: List<R>,
+    transform: (a: T, b: R) -> V
+): List<V> {
+    contract { callsInPlace(transform) }
+    val minSize = minOf(size, other.size)
+    val target = ArrayList<V>(minSize)
+    for (i in 0 until minSize) {
+        target += (transform(get(i), other[i]))
+    }
+    return target
+}
+
+/**
+ * Returns a list containing the results of applying the given [transform] function
+ * to each element in the original collection.
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T, R> List<T>.fastMapNotNull(transform: (T) -> R?): List<R> {
+    contract { callsInPlace(transform) }
+    val target = ArrayList<R>(size)
+    fastForEach { e ->
+        transform(e)?.let { target += it }
+    }
+    return target
+}
+
+/**
+ * Creates a string from all the elements separated using [separator] and using the given [prefix]
+ * and [postfix] if supplied.
+ *
+ * If the collection could be huge, you can specify a non-negative value of [limit], in which case
+ * only the first [limit] elements will be appended, followed by the [truncated] string (which
+ * defaults to "...").
+ */
+internal fun <T> List<T>.fastJoinToString(
+    separator: CharSequence = ", ",
+    prefix: CharSequence = "",
+    postfix: CharSequence = "",
+    limit: Int = -1,
+    truncated: CharSequence = "...",
+    transform: ((T) -> CharSequence)? = null
+): String {
+    return fastJoinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform)
+        .toString()
+}
+
+/**
+ * Appends the string from all the elements separated using [separator] and using the given
+ * [prefix] and [postfix] if supplied.
+ *
+ * If the collection could be huge, you can specify a non-negative value of [limit], in which
+ * case only the first [limit] elements will be appended, followed by the [truncated] string
+ * (which defaults to "...").
+ */
+private fun <T, A : Appendable> List<T>.fastJoinTo(
+    buffer: A,
+    separator: CharSequence = ", ",
+    prefix: CharSequence = "",
+    postfix: CharSequence = "",
+    limit: Int = -1,
+    truncated: CharSequence = "...",
+    transform: ((T) -> CharSequence)? = null
+): A {
+    buffer.append(prefix)
+    var count = 0
+    for (index in indices) {
+        val element = get(index)
+        if (++count > 1) buffer.append(separator)
+        if (limit < 0 || count <= limit) {
+            buffer.appendElement(element, transform)
+        } else break
+    }
+    if (limit >= 0 && count > limit) buffer.append(truncated)
+    buffer.append(postfix)
+    return buffer
+}
+
+/**
+ * Copied from Appendable.kt
+ */
+private fun <T> Appendable.appendElement(element: T, transform: ((T) -> CharSequence)?) {
+    when {
+        transform != null -> append(transform(element))
+        element is CharSequence? -> append(element)
+        element is Char -> append(element)
+        else -> append(element.toString())
+    }
+}
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
index 8fa48af8..a175918 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusManager.kt
@@ -35,6 +35,7 @@
 import androidx.compose.ui.input.pointer.consumeDownChange
 import androidx.compose.ui.input.pointer.positionChangeConsumed
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.util.fastAll
 import androidx.compose.ui.util.fastAny
 import androidx.compose.ui.util.fastForEach
 
@@ -192,7 +193,7 @@
         if (pass == PointerEventPass.Main) {
 
             if (primed &&
-                changes.all { it.changedToUp() }
+                changes.fastAll { it.changedToUp() }
             ) {
                 val pointerPxPosition: Offset = changes[0].previousPosition
                 if (changes.fastAny { !upBlockedPointers.contains(it.id) }) {
@@ -211,7 +212,7 @@
                 }
             }
 
-            if (changes.all { it.changedToDown() }) {
+            if (changes.fastAll { it.changedToDown() }) {
                 // Reset in case we were incorrectly left waiting on a delayUp message.
                 reset()
                 // If all of the changes are down, can become primed.
@@ -219,7 +220,7 @@
             }
 
             if (primed) {
-                changes.forEach {
+                changes.fastForEach {
                     if (it.changedToDown()) {
                         downPointers.add(it.id)
                     }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/AnimatedImageVector.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/AnimatedImageVector.kt
index 090152b..9d13b37 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/AnimatedImageVector.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/AnimatedImageVector.kt
@@ -20,6 +20,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.fastAssociate
 import androidx.compose.ui.graphics.painter.Painter
 import androidx.compose.ui.util.fastMaxBy
 
@@ -81,7 +82,7 @@
             val transition = updateTransition(atEnd)
             render(
                 imageVector.root,
-                targets.associate { target ->
+                targets.fastAssociate { target ->
                     target.name to target.animator.createVectorOverride(transition, totalDuration)
                 }
             )
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/Animator.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/Animator.kt
index b6c5bdb..5bc0e89 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/Animator.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/Animator.kt
@@ -29,13 +29,16 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.remember
+import androidx.compose.ui.fastZip
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.SolidColor
 import androidx.compose.ui.graphics.colorspace.ColorSpace
 import androidx.compose.ui.graphics.colorspace.ColorSpaces
+import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastMaxBy
+import androidx.compose.ui.util.fastSumBy
 import androidx.compose.ui.util.lerp
 
 internal sealed class Animator {
@@ -81,7 +84,7 @@
         overallDuration: Int,
         parentDelay: Int
     ) {
-        for (holder in holders) {
+        holders.fastForEach { holder ->
             holder.AnimateIn(
                 override,
                 transition,
@@ -100,7 +103,7 @@
 
     override val totalDuration = when (ordering) {
         Ordering.Together -> animators.fastMaxBy { it.totalDuration }?.totalDuration ?: 0
-        Ordering.Sequentially -> animators.sumBy { it.totalDuration }
+        Ordering.Sequentially -> animators.fastSumBy { it.totalDuration }
     }
 
     @Composable
@@ -112,13 +115,13 @@
     ) {
         when (ordering) {
             Ordering.Together -> {
-                for (animator in animators) {
+                animators.fastForEach { animator ->
                     animator.Configure(transition, override, overallDuration, parentDelay)
                 }
             }
             Ordering.Sequentially -> {
                 var accumulatedDelay = parentDelay
-                for (animator in animators) {
+                animators.fastForEach { animator ->
                     animator.Configure(transition, override, overallDuration, accumulatedDelay)
                     accumulatedDelay += animator.totalDuration
                 }
@@ -187,7 +190,7 @@
                 keyframes {
                     durationMillis = duration
                     delayMillis = delay
-                    for (keyframe in animatorKeyframes) {
+                    animatorKeyframes.fastForEach { keyframe ->
                         val time = (duration * keyframe.fraction).toInt()
                         addKeyframe(keyframe, time, keyframe.interpolator)
                     }
@@ -196,7 +199,7 @@
                 keyframes {
                     durationMillis = duration
                     delayMillis = overallDuration - duration - delay
-                    for (keyframe in animatorKeyframes) {
+                    animatorKeyframes.fastForEach { keyframe ->
                         val time = (duration * (1 - keyframe.fraction)).toInt()
                         addKeyframe(keyframe, time, keyframe.interpolator.transpose())
                     }
@@ -509,7 +512,7 @@
 }
 
 private fun lerp(start: List<PathNode>, stop: List<PathNode>, fraction: Float): List<PathNode> {
-    return start.zip(stop) { a, b -> lerp(a, b, fraction) }
+    return start.fastZip(stop) { a, b -> lerp(a, b, fraction) }
 }
 
 /**
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt
index adb330a..7dde65a 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/HitPathTracker.kt
@@ -62,7 +62,8 @@
     fun addHitPath(pointerId: PointerId, pointerInputFilters: List<PointerInputFilter>) {
         var parent: NodeParent = root
         var merging = true
-        eachPin@ for (pointerInputFilter in pointerInputFilters) {
+        eachPin@ for (i in pointerInputFilters.indices) {
+            val pointerInputFilter = pointerInputFilters[i]
             if (merging) {
                 val node = parent.children.find { it.pointerInputFilter == pointerInputFilter }
                 if (node != null) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputTestUtil.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputTestUtil.kt
index 4924458..e49df62 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputTestUtil.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/PointerInputTestUtil.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.util.fastForEach
 
 // TODO(shepshapard): Document.
 
@@ -128,7 +129,7 @@
 ) {
     require(pointerEvent.changes.isNotEmpty())
     require(pointerEventPasses.isNotEmpty())
-    pointerEventPasses.forEach {
+    pointerEventPasses.fastForEach {
         this.invoke(pointerEvent, it, size)
     }
 }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
index ee04406..5eddcf7 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt
@@ -21,6 +21,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
+import androidx.compose.ui.fastMapNotNull
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.platform.ViewConfiguration
@@ -341,7 +342,7 @@
         // down-ness is consumed, and we omit any pointers that previously went up entirely.
         val lastEvent = lastPointerEvent ?: return
 
-        val newChanges = lastEvent.changes.mapNotNull { old ->
+        val newChanges = lastEvent.changes.fastMapNotNull { old ->
             if (old.pressed) {
                 old.copy(
                     currentPressed = false,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/AlignmentLine.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/AlignmentLine.kt
index d41ec56..4640ac6 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/AlignmentLine.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/AlignmentLine.kt
@@ -66,6 +66,9 @@
 
 /**
  * Merges two values of the current [alignment line][AlignmentLine].
+ * This is used when a layout inherits multiple values for the same [AlignmentLine]
+ * from different children, so the position of the line within the layout will be computed
+ * by merging the children values using the provided [AlignmentLine.merger].
  */
 internal fun AlignmentLine.merge(position1: Int, position2: Int) = merger(position1, position2)
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
index d6f45c5..335f16c 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Layout.kt
@@ -37,6 +37,7 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.util.fastMap
 
 /**
  * [Layout] is the main core component for layout. It can be used to measure and position
@@ -366,7 +367,7 @@
     h: Int,
     layoutDirection: LayoutDirection
 ): Int {
-    val mapped = measurables.map {
+    val mapped = measurables.fastMap {
         DefaultIntrinsicMeasurable(it, IntrinsicMinMax.Min, IntrinsicWidthHeight.Width)
     }
     val constraints = Constraints(maxHeight = h)
@@ -385,7 +386,7 @@
     w: Int,
     layoutDirection: LayoutDirection
 ): Int {
-    val mapped = measurables.map {
+    val mapped = measurables.fastMap {
         DefaultIntrinsicMeasurable(it, IntrinsicMinMax.Min, IntrinsicWidthHeight.Height)
     }
     val constraints = Constraints(maxWidth = w)
@@ -404,7 +405,7 @@
     h: Int,
     layoutDirection: LayoutDirection
 ): Int {
-    val mapped = measurables.map {
+    val mapped = measurables.fastMap {
         DefaultIntrinsicMeasurable(it, IntrinsicMinMax.Max, IntrinsicWidthHeight.Width)
     }
     val constraints = Constraints(maxHeight = h)
@@ -423,7 +424,7 @@
     w: Int,
     layoutDirection: LayoutDirection
 ): Int {
-    val mapped = measurables.map {
+    val mapped = measurables.fastMap {
         DefaultIntrinsicMeasurable(it, IntrinsicMinMax.Max, IntrinsicWidthHeight.Height)
     }
     val constraints = Constraints(maxWidth = w)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/MeasurePolicy.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/MeasurePolicy.kt
index b6cc8e1..936d433 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/MeasurePolicy.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/MeasurePolicy.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.util.fastMap
 
 /**
  * Defines the measure and layout behavior of a [Layout]. [Layout] and [MeasurePolicy] are the way
@@ -74,7 +75,7 @@
         measurables: List<IntrinsicMeasurable>,
         height: Int
     ): Int {
-        val mapped = measurables.map {
+        val mapped = measurables.fastMap {
             DefaultIntrinsicMeasurable(it, IntrinsicMinMax.Min, IntrinsicWidthHeight.Width)
         }
         val constraints = Constraints(maxHeight = height)
@@ -92,7 +93,7 @@
         measurables: List<IntrinsicMeasurable>,
         width: Int
     ): Int {
-        val mapped = measurables.map {
+        val mapped = measurables.fastMap {
             DefaultIntrinsicMeasurable(it, IntrinsicMinMax.Min, IntrinsicWidthHeight.Height)
         }
         val constraints = Constraints(maxWidth = width)
@@ -109,7 +110,7 @@
         measurables: List<IntrinsicMeasurable>,
         height: Int
     ): Int {
-        val mapped = measurables.map {
+        val mapped = measurables.fastMap {
             DefaultIntrinsicMeasurable(it, IntrinsicMinMax.Max, IntrinsicWidthHeight.Width)
         }
         val constraints = Constraints(maxHeight = height)
@@ -126,7 +127,7 @@
         measurables: List<IntrinsicMeasurable>,
         width: Int
     ): Int {
-        val mapped = measurables.map {
+        val mapped = measurables.fastMap {
             DefaultIntrinsicMeasurable(it, IntrinsicMinMax.Max, IntrinsicWidthHeight.Height)
         }
         val constraints = Constraints(maxWidth = width)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Placeable.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Placeable.kt
index 2897c48..ebbc0b2 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Placeable.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Placeable.kt
@@ -165,7 +165,7 @@
          * have the same [zIndex] the order in which the items were placed is used.
          */
         fun Placeable.placeRelative(x: Int, y: Int, zIndex: Float = 0f) =
-            placeRelative(IntOffset(x, y), zIndex)
+            placeAutoMirrored(IntOffset(x, y), zIndex, null)
 
         /**
          * Place a [Placeable] at [x], [y] in its parent's coordinate system.
@@ -176,7 +176,8 @@
          * [zIndex] will be drawn on top of all the children with smaller [zIndex]. When children
          * have the same [zIndex] the order in which the items were placed is used.
          */
-        fun Placeable.place(x: Int, y: Int, zIndex: Float = 0f) = place(IntOffset(x, y), zIndex)
+        fun Placeable.place(x: Int, y: Int, zIndex: Float = 0f) =
+            placeApparentToRealOffset(IntOffset(x, y), zIndex, null)
 
         /**
          * Place a [Placeable] at [position] in its parent's coordinate system.
@@ -235,7 +236,7 @@
             y: Int,
             zIndex: Float = 0f,
             layerBlock: GraphicsLayerScope.() -> Unit = DefaultLayerBlock
-        ) = placeRelativeWithLayer(IntOffset(x, y), zIndex, layerBlock)
+        ) = placeAutoMirrored(IntOffset(x, y), zIndex, layerBlock)
 
         /**
          * Place a [Placeable] at [x], [y] in its parent's coordinate system with an introduced
@@ -255,7 +256,7 @@
             y: Int,
             zIndex: Float = 0f,
             layerBlock: GraphicsLayerScope.() -> Unit = DefaultLayerBlock
-        ) = placeWithLayer(IntOffset(x, y), zIndex, layerBlock)
+        ) = placeApparentToRealOffset(IntOffset(x, y), zIndex, layerBlock)
 
         /**
          * Place a [Placeable] at [position] in its parent's coordinate system with an introduced
@@ -276,10 +277,11 @@
             layerBlock: GraphicsLayerScope.() -> Unit = DefaultLayerBlock
         ) = placeApparentToRealOffset(position, zIndex, layerBlock)
 
-        private fun Placeable.placeAutoMirrored(
+        @Suppress("NOTHING_TO_INLINE")
+        internal inline fun Placeable.placeAutoMirrored(
             position: IntOffset,
             zIndex: Float,
-            layerBlock: (GraphicsLayerScope.() -> Unit)?
+            noinline layerBlock: (GraphicsLayerScope.() -> Unit)?
         ) {
             if (parentLayoutDirection == LayoutDirection.Ltr || parentWidth == 0) {
                 placeApparentToRealOffset(position, zIndex, layerBlock)
@@ -292,10 +294,11 @@
             }
         }
 
-        private fun Placeable.placeApparentToRealOffset(
+        @Suppress("NOTHING_TO_INLINE")
+        internal inline fun Placeable.placeApparentToRealOffset(
             position: IntOffset,
             zIndex: Float,
-            layerBlock: (GraphicsLayerScope.() -> Unit)?
+            noinline layerBlock: (GraphicsLayerScope.() -> Unit)?
         ) {
             placeAt(position + apparentToRealOffset, zIndex, layerBlock)
         }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/RootMeasurePolicy.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/RootMeasurePolicy.kt
index d861c46..91aec0b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/RootMeasurePolicy.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/RootMeasurePolicy.kt
@@ -21,6 +21,7 @@
 import androidx.compose.ui.unit.constrainHeight
 import androidx.compose.ui.unit.constrainWidth
 import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastMap
 
 internal object RootMeasurePolicy : LayoutNode.NoIntrinsicsMeasurePolicy(
     "Undefined intrinsics block and it is required"
@@ -43,7 +44,7 @@
                 }
             }
             else -> {
-                val placeables = measurables.map {
+                val placeables = measurables.fastMap {
                     it.measure(constraints)
                 }
                 var maxWidth = 0
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt
index 63529ac..9e961e0 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DelegatingLayoutNodeWrapper.kt
@@ -104,7 +104,7 @@
         }
     }
 
-    override fun performMeasure(constraints: Constraints): Placeable {
+    override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
         val placeable = wrapped.measure(constraints)
         measureResult = object : MeasureResult {
             override val width: Int = wrapped.measureResult.width
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt
index 0093059..1ecd191 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/InnerPlaceable.kt
@@ -40,7 +40,7 @@
 
     override val measureScope get() = layoutNode.measureScope
 
-    override fun performMeasure(constraints: Constraints): Placeable {
+    override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
         val measureResult = with(layoutNode.measurePolicy) {
             layoutNode.measureScope.measure(layoutNode.children, constraints)
         }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
index 844d0a6..f392ae6 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeWrapper.kt
@@ -24,12 +24,12 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.geometry.toRect
-import androidx.compose.ui.input.nestedscroll.NestedScrollDelegatingWrapper
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.GraphicsLayerScope
 import androidx.compose.ui.graphics.Matrix
 import androidx.compose.ui.graphics.Paint
 import androidx.compose.ui.graphics.ReusableGraphicsLayerScope
+import androidx.compose.ui.input.nestedscroll.NestedScrollDelegatingWrapper
 import androidx.compose.ui.input.pointer.PointerInputFilter
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.Measurable
@@ -50,7 +50,7 @@
 internal abstract class LayoutNodeWrapper(
     internal val layoutNode: LayoutNode
 ) : Placeable(), Measurable, LayoutCoordinates, OwnerScope, (Canvas) -> Unit {
-    internal open val wrapped: LayoutNodeWrapper? = null
+    internal open val wrapped: LayoutNodeWrapper? get() = null
     internal var wrappedBy: LayoutNodeWrapper? = null
 
     /**
@@ -69,7 +69,7 @@
         private set
 
     private var _isAttached = false
-    override val isAttached: Boolean
+    final override val isAttached: Boolean
         get() {
             if (_isAttached) {
                 require(layoutNode.isAttached)
@@ -78,35 +78,46 @@
         }
 
     private var _measureResult: MeasureResult? = null
-    open var measureResult: MeasureResult
+    var measureResult: MeasureResult
         get() = _measureResult ?: error(UnmeasuredError)
         internal set(value) {
-            if (value.width != _measureResult?.width || value.height != _measureResult?.height) {
-                val layer = layer
-                if (layer != null) {
-                    layer.resize(IntSize(value.width, value.height))
-                } else {
-                    wrappedBy?.invalidateLayer()
+            val old = _measureResult
+            if (value !== old) {
+                _measureResult = value
+                if (old == null || value.width != old.width || value.height != old.height) {
+                    onMeasureResultChanged(value.width, value.height)
                 }
-                layoutNode.owner?.onLayoutChange(layoutNode)
             }
-            _measureResult = value
-            measuredSize = IntSize(measureResult.width, measureResult.height)
         }
 
+    /**
+     * Called when the width or height of [measureResult] change. The object instance pointed to
+     * by [measureResult] may or may not have changed.
+     */
+    protected open fun onMeasureResultChanged(width: Int, height: Int) {
+        val layer = layer
+        if (layer != null) {
+            layer.resize(IntSize(width, height))
+        } else {
+            wrappedBy?.invalidateLayer()
+        }
+        layoutNode.owner?.onLayoutChange(layoutNode)
+        measuredSize = IntSize(width, height)
+    }
+
     var position: IntOffset = IntOffset.Zero
         private set
 
     var zIndex: Float = 0f
         protected set
 
-    override val parentLayoutCoordinates: LayoutCoordinates?
+    final override val parentLayoutCoordinates: LayoutCoordinates?
         get() {
             check(isAttached) { ExpectAttachedLayoutCoordinates }
             return layoutNode.outerLayoutNodeWrapper.wrappedBy
         }
 
-    override val parentCoordinates: LayoutCoordinates?
+    final override val parentCoordinates: LayoutCoordinates?
         get() {
             check(isAttached) { ExpectAttachedLayoutCoordinates }
             return wrappedBy?.getWrappedByCoordinates()
@@ -143,17 +154,12 @@
         return x >= 0f && y >= 0f && x < measuredWidth && y < measuredHeight
     }
 
-    /**
-     * Measures the modified child.
-     */
-    abstract fun performMeasure(constraints: Constraints): Placeable
-
-    /**
-     * Measures the modified child.
-     */
-    final override fun measure(constraints: Constraints): Placeable {
+    protected inline fun performingMeasure(
+        constraints: Constraints,
+        block: () -> Placeable
+    ): Placeable {
         measurementConstraints = constraints
-        val result = performMeasure(constraints)
+        val result = block()
         layer?.resize(measuredSize)
         return result
     }
@@ -434,16 +440,16 @@
      * local coordinate system.
      */
     open fun fromParentPosition(position: Offset): Offset {
+        val relativeToWrapperPosition = position - this.position
         val layer = layer
-        val targetPosition = if (layer == null) {
-            position
+        return if (layer == null) {
+            relativeToWrapperPosition
         } else {
             val inverse = matrixCache
             layer.getMatrix(inverse)
             inverse.invert()
-            inverse.map(position)
+            inverse.map(relativeToWrapperPosition)
         }
-        return targetPosition - this.position
     }
 
     protected fun drawBorder(canvas: Canvas, paint: Paint) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
index f7a0fec..33e625a 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
@@ -198,42 +198,45 @@
         var rootNodeResized = false
         if (relayoutNodes.isNotEmpty()) {
             duringMeasureLayout = true
-            relayoutNodes.popEach { layoutNode ->
-                val alignmentLinesOwner = layoutNode.alignmentLinesQueryOwner
-                if (layoutNode.isPlaced ||
-                    layoutNode.canAffectParent ||
-                    (
-                        alignmentLinesOwner != null && alignmentLinesOwner
-                            .alignmentUsageByParent != NotUsed
-                        )
-                ) {
-                    if (layoutNode.layoutState == NeedsRemeasure) {
-                        if (doRemeasure(layoutNode, rootConstraints)) {
-                            rootNodeResized = true
-                        }
-                    }
-                    if (layoutNode.layoutState == NeedsRelayout && layoutNode.isPlaced) {
-                        if (layoutNode === root) {
-                            layoutNode.place(0, 0)
-                        } else {
-                            layoutNode.replace()
-                        }
-                        onPositionedDispatcher.onNodePositioned(layoutNode)
-                        consistencyChecker?.assertConsistent()
-                    }
-                    measureIteration++
-                    // execute postponed `onRequestMeasure`
-                    if (postponedMeasureRequests.isNotEmpty()) {
-                        postponedMeasureRequests.fastForEach {
-                            if (it.isAttached) {
-                                requestRemeasure(it)
+            try {
+                relayoutNodes.popEach { layoutNode ->
+                    val alignmentLinesOwner = layoutNode.alignmentLinesQueryOwner
+                    if (layoutNode.isPlaced ||
+                        layoutNode.canAffectParent ||
+                        (
+                            alignmentLinesOwner != null && alignmentLinesOwner
+                                .alignmentUsageByParent != NotUsed
+                            )
+                    ) {
+                        if (layoutNode.layoutState == NeedsRemeasure) {
+                            if (doRemeasure(layoutNode, rootConstraints)) {
+                                rootNodeResized = true
                             }
                         }
-                        postponedMeasureRequests.clear()
+                        if (layoutNode.layoutState == NeedsRelayout && layoutNode.isPlaced) {
+                            if (layoutNode === root) {
+                                layoutNode.place(0, 0)
+                            } else {
+                                layoutNode.replace()
+                            }
+                            onPositionedDispatcher.onNodePositioned(layoutNode)
+                            consistencyChecker?.assertConsistent()
+                        }
+                        measureIteration++
+                        // execute postponed `onRequestMeasure`
+                        if (postponedMeasureRequests.isNotEmpty()) {
+                            postponedMeasureRequests.fastForEach {
+                                if (it.isAttached) {
+                                    requestRemeasure(it)
+                                }
+                            }
+                            postponedMeasureRequests.clear()
+                        }
                     }
                 }
+            } finally {
+                duringMeasureLayout = false
             }
-            duringMeasureLayout = false
             consistencyChecker?.assertConsistent()
         }
         return rootNodeResized
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedDrawNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedDrawNode.kt
index 28a685a..cca6cd7 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedDrawNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedDrawNode.kt
@@ -21,7 +21,6 @@
 import androidx.compose.ui.draw.DrawModifier
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Canvas
-import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.toSize
 
@@ -80,16 +79,10 @@
             invalidateCache = true
         }
 
-    override var measureResult: MeasureResult
-        get() = super.measureResult
-        set(value) {
-            if (super.measuredSize.width != value.width ||
-                super.measuredSize.height != value.height
-            ) {
-                invalidateCache = true
-            }
-            super.measureResult = value
-        }
+    override fun onMeasureResultChanged(width: Int, height: Int) {
+        super.onMeasureResultChanged(width, height)
+        invalidateCache = true
+    }
 
     // This is not thread safe
     override fun performDraw(canvas: Canvas) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedLayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedLayoutNode.kt
index ec54573..614d548 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedLayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/ModifiedLayoutNode.kt
@@ -32,9 +32,11 @@
     modifier: LayoutModifier
 ) : DelegatingLayoutNodeWrapper<LayoutModifier>(wrapped, modifier) {
 
-    override fun performMeasure(constraints: Constraints): Placeable = with(modifier) {
-        measureResult = measureScope.measure(wrapped, constraints)
-        this@ModifiedLayoutNode
+    override fun measure(constraints: Constraints): Placeable = performingMeasure(constraints) {
+        with(modifier) {
+            measureResult = measureScope.measure(wrapped, constraints)
+            this@ModifiedLayoutNode
+        }
     }
 
     override fun minIntrinsicWidth(height: Int): Int =
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/RemeasureModifierWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/RemeasureModifierWrapper.kt
index f37eb1b..fdb9840 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/RemeasureModifierWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/RemeasureModifierWrapper.kt
@@ -27,8 +27,8 @@
     wrapped: LayoutNodeWrapper,
     modifier: OnRemeasuredModifier
 ) : DelegatingLayoutNodeWrapper<OnRemeasuredModifier>(wrapped, modifier) {
-    override fun performMeasure(constraints: Constraints): Placeable {
-        val placeable = super.performMeasure(constraints)
+    override fun measure(constraints: Constraints): Placeable {
+        val placeable = super.measure(constraints)
         val invokeRemeasureCallbacks = {
             modifier.onRemeasured(measuredSize)
         }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsNode.kt
index ffac306..2c922f0 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsNode.kt
@@ -51,7 +51,7 @@
     /*
      * This is expected to be the outermost semantics modifier on a layout node.
      */
-    internal val layoutNodeWrapper: SemanticsWrapper,
+    internal val outerSemanticsNodeWrapper: SemanticsWrapper,
     /**
      * mergingEnabled specifies whether mergeDescendants config has any effect.
      *
@@ -64,13 +64,13 @@
      */
     val mergingEnabled: Boolean
 ) {
-    internal val unmergedConfig = layoutNodeWrapper.collapsedSemanticsConfiguration()
-    val id: Int = layoutNodeWrapper.modifier.id
+    internal val unmergedConfig = outerSemanticsNodeWrapper.collapsedSemanticsConfiguration()
+    val id: Int = outerSemanticsNodeWrapper.modifier.id
 
     /**
      * The [LayoutInfo] that this is associated with.
      */
-    val layoutInfo: LayoutInfo = layoutNodeWrapper.layoutNode
+    val layoutInfo: LayoutInfo get() = layoutNode
 
     /**
      * The [root][RootForTest] this node is attached to.
@@ -80,60 +80,45 @@
     /**
      * The [LayoutNode] that this is associated with.
      */
-    internal val layoutNode: LayoutNode = layoutNodeWrapper.layoutNode
+    internal val layoutNode: LayoutNode = outerSemanticsNodeWrapper.layoutNode
 
     // GEOMETRY
 
     /**
      * The size of the bounding box for this node, with no clipping applied
      */
-    val size: IntSize
-        get() {
-            return this.layoutNode.coordinates.size
-        }
+    val size: IntSize get() = findWrapperToGetBounds().size
 
     /**
      * The bounding box for this node relative to the root of this Compose hierarchy, with
      * clipping applied. To get the bounds with no clipping applied, use
      * Rect([positionInRoot], [size].toSize())
      */
-    val boundsInRoot: Rect
-        get() {
-            return this.layoutNode.coordinates.boundsInRoot()
-        }
+    val boundsInRoot: Rect get() = this.findWrapperToGetBounds().boundsInRoot()
 
     /**
      * The position of this node relative to the root of this Compose hierarchy, with no clipping
      * applied
      */
-    val positionInRoot: Offset
-        get() {
-            return this.layoutNode.coordinates.positionInRoot()
-        }
+    val positionInRoot: Offset get() = findWrapperToGetBounds().positionInRoot()
 
     /**
      * The bounding box for this node relative to the screen, with clipping applied. To get the
      * bounds with no clipping applied, use PxBounds([positionInWindow], [size].toSize())
      */
-    val boundsInWindow: Rect
-        get() {
-            return this.layoutNode.coordinates.boundsInWindow()
-        }
+    val boundsInWindow: Rect get() = findWrapperToGetBounds().boundsInWindow()
 
     /**
      * The position of this node relative to the screen, with no clipping applied
      */
-    val positionInWindow: Offset
-        get() {
-            return this.layoutNode.coordinates.positionInWindow()
-        }
+    val positionInWindow: Offset get() = findWrapperToGetBounds().positionInWindow()
 
     /**
      * Returns the position of an [alignment line][AlignmentLine], or [AlignmentLine.Unspecified]
      * if the line is not provided.
      */
     fun getAlignmentLinePosition(alignmentLine: AlignmentLine): Int {
-        return this.layoutNode.coordinates[alignmentLine]
+        return findWrapperToGetBounds()[alignmentLine]
     }
 
     // CHILDREN
@@ -280,28 +265,46 @@
         }
         return list
     }
+
+    /**
+     * If the node is merging the descendants, we'll use the outermost semantics modifier that has
+     * mergeDescendants == true to report the bounds, size and position of the node. For majority
+     * of use cases it means that accessibility bounds will be equal to the clickable area.
+     * Otherwise the outermost semantics will be used to report bounds, size and position.
+     */
+    private fun findWrapperToGetBounds(): LayoutNodeWrapper {
+        return if (isMergingSemanticsOfDescendants) {
+            layoutNode.outerMergingSemantics ?: outerSemanticsNodeWrapper
+        } else {
+            outerSemanticsNodeWrapper
+        }
+    }
 }
 
 /**
  * Returns the outermost semantics node on a LayoutNode.
  */
 internal val LayoutNode.outerSemantics: SemanticsWrapper?
-    get() {
-        return outerLayoutNodeWrapper.nearestSemantics
+    get() = outerLayoutNodeWrapper.nearestSemantics { true }
+
+internal val LayoutNode.outerMergingSemantics
+    get() = outerLayoutNodeWrapper.nearestSemantics {
+        it.modifier.semanticsConfiguration.isMergingSemanticsOfDescendants
     }
 
 /**
  * Returns the nearest semantics wrapper starting from a LayoutNodeWrapper.
  */
-internal val LayoutNodeWrapper.nearestSemantics: SemanticsWrapper?
-    get() {
-        var wrapper: LayoutNodeWrapper? = this
-        while (wrapper != null) {
-            if (wrapper is SemanticsWrapper) return wrapper
-            wrapper = wrapper.wrapped
-        }
-        return null
+internal inline fun LayoutNodeWrapper.nearestSemantics(
+    predicate: (SemanticsWrapper) -> Boolean
+): SemanticsWrapper? {
+    var wrapper: LayoutNodeWrapper? = this
+    while (wrapper != null) {
+        if (wrapper is SemanticsWrapper && predicate(wrapper)) return wrapper
+        wrapper = wrapper.wrapped
     }
+    return null
+}
 
 internal fun SemanticsNode.findChildById(id: Int): SemanticsNode? {
     if (this.id == id) return this
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
index bc63852..3e404c8 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsProperties.kt
@@ -148,7 +148,7 @@
      *
      * @see SemanticsPropertyReceiver.role
      */
-    val Role = SemanticsPropertyKey<Role>("Role")
+    val Role = SemanticsPropertyKey<Role>("Role") { parentValue, _ -> parentValue }
 
     /**
      * @see SemanticsPropertyReceiver.testTag
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsWrapper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsWrapper.kt
index 3895379..ead258a 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsWrapper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsWrapper.kt
@@ -24,7 +24,7 @@
     semanticsModifier: SemanticsModifier
 ) : DelegatingLayoutNodeWrapper<SemanticsModifier>(wrapped, semanticsModifier) {
     fun collapsedSemanticsConfiguration(): SemanticsConfiguration {
-        val nextSemantics = wrapped.nearestSemantics
+        val nextSemantics = wrapped.nearestSemantics { true }
         if (nextSemantics == null || modifier.semanticsConfiguration.isClearingSemantics) {
             return modifier.semanticsConfiguration
         }
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index b7ab56a..79dae26 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -1559,6 +1559,7 @@
   public final class HandlerCompat {
     method public static android.os.Handler createAsync(android.os.Looper);
     method public static android.os.Handler createAsync(android.os.Looper, android.os.Handler.Callback);
+    method @RequiresApi(16) public static boolean hasCallbacks(android.os.Handler, Runnable);
     method public static boolean postDelayed(android.os.Handler, Runnable, Object?, long);
   }
 
diff --git a/core/core/api/public_plus_experimental_current.txt b/core/core/api/public_plus_experimental_current.txt
index 28165c4..2db02d0 100644
--- a/core/core/api/public_plus_experimental_current.txt
+++ b/core/core/api/public_plus_experimental_current.txt
@@ -1557,6 +1557,7 @@
   public final class HandlerCompat {
     method public static android.os.Handler createAsync(android.os.Looper);
     method public static android.os.Handler createAsync(android.os.Looper, android.os.Handler.Callback);
+    method @RequiresApi(16) public static boolean hasCallbacks(android.os.Handler, Runnable);
     method public static boolean postDelayed(android.os.Handler, Runnable, Object?, long);
   }
 
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index edda7df..3fb7af8 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -1869,6 +1869,7 @@
   public final class HandlerCompat {
     method public static android.os.Handler createAsync(android.os.Looper);
     method public static android.os.Handler createAsync(android.os.Looper, android.os.Handler.Callback);
+    method @RequiresApi(16) public static boolean hasCallbacks(android.os.Handler, Runnable);
     method public static boolean postDelayed(android.os.Handler, Runnable, Object?, long);
   }
 
diff --git a/core/core/build.gradle b/core/core/build.gradle
index f776c9cf..80c146b 100644
--- a/core/core/build.gradle
+++ b/core/core/build.gradle
@@ -10,7 +10,7 @@
 }
 
 dependencies {
-    api("androidx.annotation:annotation:1.2.0-rc01")
+    api("androidx.annotation:annotation:1.2.0")
     api("androidx.lifecycle:lifecycle-runtime:2.0.0")
     api("androidx.versionedparcelable:versionedparcelable:1.1.1")
     implementation("androidx.collection:collection:1.0.0")
diff --git a/core/core/src/androidTest/java/androidx/core/os/HandlerCompatTest.java b/core/core/src/androidTest/java/androidx/core/os/HandlerCompatTest.java
index a934359..42c01c2 100644
--- a/core/core/src/androidTest/java/androidx/core/os/HandlerCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/os/HandlerCompatTest.java
@@ -24,6 +24,7 @@
 
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.Message;
 
 import androidx.test.filters.MediumTest;
@@ -36,21 +37,26 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
 @MediumTest
 public final class HandlerCompatTest {
     private final HandlerThread mThread = new HandlerThread("handler-compat-test");
 
-    @Before public void before() {
+    @Before
+    public void before() {
         mThread.start();
     }
 
-    @After public void after() {
+    @After
+    public void after() {
         assertTrue(mThread.quit());
     }
 
-    @Test public void postDelayedWithToken() throws InterruptedException {
+    @Test
+    public void postDelayedWithToken() throws InterruptedException {
         final Handler handler = new Handler(mThread.getLooper());
 
         // Schedule a latch at 300ms to block the test thread.
@@ -94,7 +100,8 @@
         assertEquals(asList("0", "100"), events);
     }
 
-    @Test public void createAsyncAllApiLevels() throws InterruptedException {
+    @Test
+    public void createAsyncAllApiLevels() throws InterruptedException {
         Handler handler = HandlerCompat.createAsync(mThread.getLooper());
 
         final CountDownLatch latch = new CountDownLatch(1);
@@ -110,8 +117,9 @@
         assertTrue(latch.await(1, SECONDS));
     }
 
-    @SdkSuppress(minSdkVersion = 16)
-    @Test public void createAsyncWhenAsyncAvailable() throws InterruptedException {
+    @SdkSuppress(minSdkVersion = 17)
+    @Test
+    public void createAsyncWhenAsyncAvailable() throws InterruptedException {
         Handler handler = HandlerCompat.createAsync(mThread.getLooper());
 
         final CountDownLatch latch = new CountDownLatch(1);
@@ -132,7 +140,8 @@
         assertTrue(isAsync.get());
     }
 
-    @Test public void createAsyncWithCallbackAllApiLevels() throws InterruptedException {
+    @Test
+    public void createAsyncWithCallbackAllApiLevels() throws InterruptedException {
         final CountDownLatch latch = new CountDownLatch(1);
         Handler handler = HandlerCompat.createAsync(mThread.getLooper(), new Handler.Callback() {
             @Override
@@ -146,8 +155,9 @@
         assertTrue(latch.await(1, SECONDS));
     }
 
-    @SdkSuppress(minSdkVersion = 16)
-    @Test public void createAsyncWithCallbackWhenAsyncAvailable() throws InterruptedException {
+    @SdkSuppress(minSdkVersion = 17)
+    @Test
+    public void createAsyncWithCallbackWhenAsyncAvailable() throws InterruptedException {
         final CountDownLatch latch = new CountDownLatch(1);
         final AtomicReference<Boolean> isAsync = new AtomicReference<>();
         Handler handler = HandlerCompat.createAsync(mThread.getLooper(), new Handler.Callback() {
@@ -163,4 +173,43 @@
         assertTrue(latch.await(1, SECONDS));
         assertTrue(isAsync.get());
     }
+
+    @SdkSuppress(minSdkVersion = 16)
+    @Test
+    public void testHasCallbacks() {
+        Runnable r = new Runnable() {
+            @Override
+            public void run() {
+                // Meh?
+            }
+        };
+
+        PausedLooper looperThread = new PausedLooper();
+        looperThread.start();
+
+        Handler handler = looperThread.getHandler(1000);
+        handler.post(r);
+
+        assertTrue("Handler has callback for r", HandlerCompat.hasCallbacks(handler, r));
+    }
+
+    private static class PausedLooper extends Thread {
+        private final Semaphore mHandlerLock = new Semaphore(0);
+        private Handler mHandler;
+
+        @Override
+        public void run() {
+            Looper.prepare();
+            mHandler = new Handler(Looper.myLooper());
+            mHandlerLock.release();
+        }
+
+        public Handler getHandler(long timeout) {
+            try {
+                mHandlerLock.tryAcquire(1, timeout, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException ignored) {
+            }
+            return mHandler;
+        }
+    }
 }
diff --git a/core/core/src/main/java/androidx/core/os/HandlerCompat.java b/core/core/src/main/java/androidx/core/os/HandlerCompat.java
index 26b6035..3ca278f4 100644
--- a/core/core/src/main/java/androidx/core/os/HandlerCompat.java
+++ b/core/core/src/main/java/androidx/core/os/HandlerCompat.java
@@ -24,8 +24,10 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 
 import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 
 /**
  * Helper for accessing features in {@link Handler}.
@@ -40,25 +42,41 @@
      * <p>Messages sent to an async handler are guaranteed to be ordered with respect to one
      * another, but not necessarily with respect to messages from other Handlers.</p>
      *
-     * @see #createAsync(Looper, Callback) to create an async Handler with custom message handling.
+     * @see Handler#createAsync(Looper, Handler.Callback) to create an async Handler with custom
+     * message handling.
+     *
+     * Compatibility behavior:
+     * <ul>
+     * <li>SDK 28 and above, this method matches platform behavior.
+     * <li>SDK 17 through 27, this method attempts to call the platform API via reflection, but
+     * may fail and return a synchronous handler instance.
+     * <li>Below SDK 17, this method will always return a synchronous handler instance.
+     * </ul>
      *
      * @param looper the Looper that the new Handler should be bound to
      * @return a new async Handler instance
      * @see Handler#createAsync(Looper)
      */
+    @SuppressWarnings("JavaReflectionMemberAccess")
     @NonNull
     public static Handler createAsync(@NonNull Looper looper) {
+        Exception wrappedException;
+
         if (Build.VERSION.SDK_INT >= 28) {
-            return Handler.createAsync(looper);
-        }
-        if (Build.VERSION.SDK_INT >= 16) {
+            return Api28Impl.createAsync(looper);
+        } else if (Build.VERSION.SDK_INT >= 17) {
             try {
+                // This constructor was added as private in JB MR1:
+                // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/jb-mr1-release/core/java/android/os/Handler.java
                 return Handler.class.getDeclaredConstructor(Looper.class, Handler.Callback.class,
                         boolean.class)
                         .newInstance(looper, null, true);
-            } catch (IllegalAccessException ignored) {
-            } catch (InstantiationException ignored) {
-            } catch (NoSuchMethodException ignored) {
+            } catch (IllegalAccessException e) {
+                wrappedException = e;
+            } catch (InstantiationException e) {
+                wrappedException = e;
+            } catch (NoSuchMethodException e) {
+                wrappedException = e;
             } catch (InvocationTargetException e) {
                 Throwable cause = e.getCause();
                 if (cause instanceof RuntimeException) {
@@ -69,7 +87,10 @@
                 }
                 throw new RuntimeException(cause);
             }
-            Log.v(TAG, "Unable to invoke Handler(Looper, Callback, boolean) constructor");
+            // This is a non-fatal failure, but it affects behavior and may be relevant when
+            // investigating issue reports.
+            Log.w(TAG, "Unable to invoke Handler(Looper, Callback, boolean) constructor",
+                    wrappedException);
         }
         return new Handler(looper);
     }
@@ -83,23 +104,38 @@
      *
      * @see #createAsync(Looper) to create an async Handler without custom message handling.
      *
+     * Compatibility behavior:
+     * <ul>
+     * <li>SDK 28 and above, this method matches platform behavior.
+     * <li>SDK 17 through 27, this method attempts to call the platform API via reflection, but
+     * may fail and return a synchronous handler instance.
+     * <li>Below SDK 17, this method will always return a synchronous handler instance.
+     * </ul>
+     *
      * @param looper the Looper that the new Handler should be bound to
      * @return a new async Handler instance
-     * @see Handler#createAsync(Looper, Callback)
+     * @see Handler#createAsync(Looper, Handler.Callback)
      */
+    @SuppressWarnings("JavaReflectionMemberAccess")
     @NonNull
     public static Handler createAsync(@NonNull Looper looper, @NonNull Handler.Callback callback) {
+        Exception wrappedException;
+
         if (Build.VERSION.SDK_INT >= 28) {
-            return Handler.createAsync(looper, callback);
-        }
-        if (Build.VERSION.SDK_INT >= 16) {
+            return Api28Impl.createAsync(looper, callback);
+        } else if (Build.VERSION.SDK_INT >= 17) {
             try {
+                // This constructor was added as private API in JB MR1:
+                // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/jb-mr1-release/core/java/android/os/Handler.java
                 return Handler.class.getDeclaredConstructor(Looper.class, Handler.Callback.class,
                         boolean.class)
                         .newInstance(looper, callback, true);
-            } catch (IllegalAccessException ignored) {
-            } catch (InstantiationException ignored) {
-            } catch (NoSuchMethodException ignored) {
+            } catch (IllegalAccessException e) {
+                wrappedException = e;
+            } catch (InstantiationException e) {
+                wrappedException = e;
+            } catch (NoSuchMethodException e) {
+                wrappedException = e;
             } catch (InvocationTargetException e) {
                 Throwable cause = e.getCause();
                 if (cause instanceof RuntimeException) {
@@ -110,7 +146,10 @@
                 }
                 throw new RuntimeException(cause);
             }
-            Log.v(TAG, "Unable to invoke Handler(Looper, Callback, boolean) constructor");
+            // This is a non-fatal failure, but it affects behavior and may be relevant when
+            // investigating issue reports.
+            Log.w(TAG, "Unable to invoke Handler(Looper, Callback, boolean) constructor",
+                    wrappedException);
         }
         return new Handler(looper, callback);
     }
@@ -141,7 +180,7 @@
     public static boolean postDelayed(@NonNull Handler handler, @NonNull Runnable r,
             @Nullable Object token, long delayMillis) {
         if (Build.VERSION.SDK_INT >= 28) {
-            return handler.postDelayed(r, token, delayMillis);
+            return Api28Impl.postDelayed(handler, r, token, delayMillis);
         }
 
         Message message = Message.obtain(handler, r);
@@ -149,6 +188,92 @@
         return handler.sendMessageDelayed(message, delayMillis);
     }
 
+    /**
+     * Checks if there are any pending posts of messages with callback {@code r} in
+     * the message queue.
+     *
+     * Compatibility behavior:
+     * <ul>
+     * <li>SDK 29 and above, this method matches platform behavior.
+     * <li>SDK 16 through 28, this method attempts to call the platform API via reflection, but
+     * will throw an unchecked exception if the method has been altered from the AOSP
+     * implementation and cannot be called. This is unlikely, but there is no safe fallback case
+     * for this method and we must throw an exception as a result.
+     * </ul>
+     *
+     * @param handler handler on which to call the method
+     * @param r callback to look for in the message queue
+     * @return {@code true} if the callback is in the message queue
+     * @see Handler#hasCallbacks(Runnable)
+     */
+    @RequiresApi(16)
+    public static boolean hasCallbacks(@NonNull Handler handler, @NonNull Runnable r) {
+        Exception wrappedException = null;
+
+        if (Build.VERSION.SDK_INT >= 29) {
+            return Api29Impl.hasCallbacks(handler, r);
+        } else if (Build.VERSION.SDK_INT >= 16) {
+            // The method signature didn't change when it was made public in SDK 29, but use
+            // reflection so that we don't cause a verification error or NotFound exception if an
+            // OEM changed something.
+            try {
+                Method hasCallbacksMethod = Handler.class.getMethod("hasCallbacks", Runnable.class);
+                //noinspection ConstantConditions
+                return (boolean) hasCallbacksMethod.invoke(handler, r);
+            } catch (InvocationTargetException e) {
+                Throwable cause = e.getCause();
+                if (cause instanceof RuntimeException) {
+                    throw ((RuntimeException) cause);
+                }
+                if (cause instanceof Error) {
+                    throw ((Error) cause);
+                }
+                throw new RuntimeException(cause);
+            } catch (IllegalAccessException e) {
+                wrappedException = e;
+            } catch (NoSuchMethodException e) {
+                wrappedException = e;
+            } catch (NullPointerException e) {
+                wrappedException = e;
+            }
+        }
+
+        throw new UnsupportedOperationException("Failed to call Handler.hasCallbacks(), but there"
+                + " is no safe failure mode for this method. Raising exception.", wrappedException);
+    }
+
     private HandlerCompat() {
+        // Non-instantiable.
+    }
+
+    @RequiresApi(29)
+    private static class Api29Impl {
+        private Api29Impl() {
+            // Non-instantiable.
+        }
+
+        public static boolean hasCallbacks(Handler handler, Runnable r) {
+            return handler.hasCallbacks(r);
+        }
+    }
+
+    @RequiresApi(28)
+    private static class Api28Impl {
+        private Api28Impl() {
+            // Non-instantiable.
+        }
+
+        public static Handler createAsync(Looper looper) {
+            return Handler.createAsync(looper);
+        }
+
+        public static Handler createAsync(Looper looper, Handler.Callback callback) {
+            return Handler.createAsync(looper, callback);
+        }
+
+        public static boolean postDelayed(Handler handler, Runnable r, Object token,
+                long delayMillis) {
+            return handler.postDelayed(r, token, delayMillis);
+        }
     }
 }
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 0c470ae..4c612db 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -208,6 +208,7 @@
 Exception while resolving link to Module: Package:androidx\.compose\.ui\.graphics\.vector Function:group Receiver:<this>
 Exception while resolving link to Module: Package:androidx\.compose\.ui\.graphics\.vector Function:path Receiver:<this>
 Exception while resolving link to Module: Package:androidx\.compose\.ui\.unit ExternalClass:kotlin\.Int Function:times Receiver:<this>
+Exception while resolving link to Module: Package:androidx.lifecycle ExternalClass:kotlinx.coroutines.flow.Flow Function:flowWithLifecycle Receiver:<this>
 # > Task :icing:extractIncludeDebugAndroidTestProto
 proto file '[^ ]*' directly specified in configuration\. It's likely you specified files\('path/to/foo\.proto'\) or fileTree\('path/to/directory'\) in protobuf or compile configuration\. This makes you vulnerable to https://github\.com/google/protobuf\-gradle\-plugin/issues/[0-9][0-9]*\. Please use files\('path/to/directory'\) instead\.
 OUT_DIR=\$OUT_DIR
@@ -327,6 +328,7 @@
 # > Task :support-preference-demos:compileDebugJavaWithJavac
 # > Task :startup:integration-tests:first-library:processDebugManifest
 \$SUPPORT/startup/integration\-tests/first\-library/src/main/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
+meta\-data\#androidx\.work\.impl\.WorkManagerInitializer was tagged at AndroidManifest\.xml\:[0-9]+ to remove other declarations but no other declaration present
 provider\#androidx\.work\.impl\.WorkManagerInitializer was tagged at AndroidManifest\.xml:[0-9]+ to remove other declarations but no other declaration present
 # > Task :camera:integration-tests:camera-testapp-extensions:compileDebugJavaWithJavac
 # > Task :camera:integration-tests:camera-testapp-core:compileDebugJavaWithJavac
@@ -361,6 +363,8 @@
 Build testing_surface_format_.*
 # > Task :collection:collection-benchmark:processReleaseAndroidTestManifest
 \$OUT_DIR/androidx/collection/collection\-benchmark/build/intermediates/tmp/manifest/androidTest/release/manifestMerger[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
+# > Task :emoji2:emoji2-benchmark:processReleaseAndroidTestManifest
+\$OUT_DIR/androidx/emoji2/emoji2\-benchmark/build/intermediates/tmp/manifest/androidTest/release/manifestMerger[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 # > Task :gridlayout:gridlayout:compileDebugAndroidTestJavaWithJavac
 # > Task :leanback:leanback:processDebugAndroidTestManifest
 Package name 'androidx\.testutils' used in: :internal\-testutils\-runtime, :internal\-testutils\-espresso\.
@@ -525,13 +529,15 @@
 # > Task :compose:ui:ui:processDebugUnitTestManifest
 \$OUT_DIR/androidx/compose/ui/ui/build/intermediates/tmp/manifest/test/debug/manifestMerger[0-9]+\.xml Warning:
 # > Task *:lintDebug
-\$SUPPORT/.*/build\.gradle: Ignore: Unknown issue id "ComposableNaming" \[UnknownIssueId\]
-\$SUPPORT/.*/build\.gradle: Ignore: Unknown issue id "ComposableLambdaParameterNaming" \[UnknownIssueId\]
-\$SUPPORT/.*/build\.gradle: Ignore: Unknown issue id "ComposableLambdaParameterPosition" \[UnknownIssueId\]
-\$SUPPORT/.*/build\.gradle: Ignore: Unknown issue id "CompositionLocalNaming" \[UnknownIssueId\]
-Explanation for issues of type "UnknownIssueId":
-Lint will report this issue if it is configured with an issue id it does
-not recognize in for example Gradle files or lint\.xml configuration files\.
+# TODO: b/182321297 remove when this message isn't printed
+Lint: Unknown issue id "ComposableNaming"
+Lint: Unknown issue id "ComposableLambdaParameterNaming"
+Lint: Unknown issue id "ComposableLambdaParameterPosition"
+Lint: Unknown issue id "CompositionLocalNaming"
+Lint: Unknown issue id "ComposableModifierFactory"
+Lint: Unknown issue id "ModifierFactoryExtensionFunction"
+Lint: Unknown issue id "ModifierFactoryReturnType"
+Lint: Unknown issue id "ModifierParameter"
 # > Task :compose:foundation:foundation:reportLibraryMetrics
 Stripped invalid locals information from [0-9]+ methods\.
 Methods with invalid locals information:
diff --git a/development/file-utils/diff-filterer.py b/development/file-utils/diff-filterer.py
index d1329bf..dd575a8 100755
--- a/development/file-utils/diff-filterer.py
+++ b/development/file-utils/diff-filterer.py
@@ -715,10 +715,17 @@
 
   def cleanupTempDirs(self):
     print("Clearing work directories")
-    if os.path.isdir(self.workPath):
-      for child in os.listdir(self.workPath):
-        if child.startswith("job-"):
-          fileIo.removePath(os.path.join(self.workPath, child))
+    numAttempts = 3
+    for attempt in range(numAttempts):
+      if os.path.isdir(self.workPath):
+        for child in os.listdir(self.workPath):
+          if child.startswith("job-"):
+            path = os.path.join(self.workPath, child)
+            try:
+              fileIo.removePath(path)
+            except IOError as e:
+              if attempt >= numAttempts - 1:
+                raise Exception("Failed to remove " + path, e)
 
   def runnerTest(self, testState, timeout = None):
     workPath = self.getWorkPath(0)
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 20a697b..94ebd87 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -10,10 +10,10 @@
     docs("androidx.activity:activity-ktx:1.3.0-alpha04")
     docs("androidx.ads:ads-identifier:1.0.0-alpha04")
     docs("androidx.ads:ads-identifier-provider:1.0.0-alpha04")
-    docs("androidx.annotation:annotation:1.2.0-rc01")
-    docs("androidx.annotation:annotation-experimental:1.1.0-rc01")
-    docs("androidx.appcompat:appcompat:1.3.0-beta01")
-    docs("androidx.appcompat:appcompat-resources:1.3.0-beta01")
+    docs("androidx.annotation:annotation:1.2.0")
+    docs("androidx.annotation:annotation-experimental:1.1.0-rc02")
+    docs("androidx.appcompat:appcompat:1.3.0-rc01")
+    docs("androidx.appcompat:appcompat-resources:1.3.0-rc01")
     docs("androidx.arch.core:core-common:2.1.0")
     docs("androidx.arch.core:core-runtime:2.1.0")
     docs("androidx.arch.core:core-testing:2.1.0")
@@ -81,10 +81,10 @@
     docs("androidx.contentpager:contentpager:1.0.0")
     docs("androidx.coordinatorlayout:coordinatorlayout:1.1.0")
     docs("androidx.core:core-role:1.1.0-alpha02")
-    docs("androidx.core:core:1.5.0-beta03")
     docs("androidx.core:core-animation:1.0.0-alpha02")
     docs("androidx.core:core-animation-testing:1.0.0-alpha02")
-    docs("androidx.core:core-ktx:1.5.0-beta03")
+    docs("androidx.core:core:1.5.0-rc01")
+    docs("androidx.core:core-ktx:1.5.0-rc01")
     docs("androidx.cursoradapter:cursoradapter:1.0.0")
     docs("androidx.customview:customview:1.1.0")
     docs("androidx.datastore:datastore:1.0.0-alpha08")
@@ -105,9 +105,9 @@
     docs("androidx.enterprise:enterprise-feedback:1.1.0")
     docs("androidx.enterprise:enterprise-feedback-testing:1.1.0")
     docs("androidx.exifinterface:exifinterface:1.3.2")
-    docs("androidx.fragment:fragment:1.3.1")
-    docs("androidx.fragment:fragment-ktx:1.3.1")
-    docs("androidx.fragment:fragment-testing:1.3.1")
+    docs("androidx.fragment:fragment:1.3.2")
+    docs("androidx.fragment:fragment-ktx:1.3.2")
+    docs("androidx.fragment:fragment-testing:1.3.2")
     docs("androidx.gridlayout:gridlayout:1.0.0")
     docs("androidx.heifwriter:heifwriter:1.1.0-alpha01")
     docs("androidx.hilt:hilt-common:1.0.0-beta01")
@@ -121,24 +121,24 @@
     docs("androidx.leanback:leanback-paging:1.1.0-alpha07")
     docs("androidx.leanback:leanback-preference:1.1.0-beta01")
     docs("androidx.leanback:leanback-tab:1.1.0-beta01")
-    docs("androidx.lifecycle:lifecycle-common:2.3.0")
-    docs("androidx.lifecycle:lifecycle-common-java8:2.3.0")
+    docs("androidx.lifecycle:lifecycle-common:2.3.1")
+    docs("androidx.lifecycle:lifecycle-common-java8:2.3.1")
     docs("androidx.lifecycle:lifecycle-extensions:2.2.0")
-    docs("androidx.lifecycle:lifecycle-livedata:2.3.0")
-    docs("androidx.lifecycle:lifecycle-livedata-core:2.3.0")
-    docs("androidx.lifecycle:lifecycle-livedata-core-ktx:2.3.0")
-    docs("androidx.lifecycle:lifecycle-livedata-ktx:2.3.0")
-    docs("androidx.lifecycle:lifecycle-process:2.3.0")
-    docs("androidx.lifecycle:lifecycle-reactivestreams:2.3.0")
-    docs("androidx.lifecycle:lifecycle-reactivestreams-ktx:2.3.0")
-    docs("androidx.lifecycle:lifecycle-runtime:2.3.0")
-    docs("androidx.lifecycle:lifecycle-runtime-ktx:2.3.0")
-    docs("androidx.lifecycle:lifecycle-runtime-testing:2.3.0")
-    docs("androidx.lifecycle:lifecycle-service:2.3.0")
-    docs("androidx.lifecycle:lifecycle-viewmodel:2.3.0")
+    docs("androidx.lifecycle:lifecycle-livedata:2.3.1")
+    docs("androidx.lifecycle:lifecycle-livedata-core:2.3.1")
+    docs("androidx.lifecycle:lifecycle-livedata-core-ktx:2.3.1")
+    docs("androidx.lifecycle:lifecycle-livedata-ktx:2.3.1")
+    docs("androidx.lifecycle:lifecycle-process:2.3.1")
+    docs("androidx.lifecycle:lifecycle-reactivestreams:2.3.1")
+    docs("androidx.lifecycle:lifecycle-reactivestreams-ktx:2.3.1")
+    docs("androidx.lifecycle:lifecycle-runtime:2.3.1")
+    docs("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1")
+    docs("androidx.lifecycle:lifecycle-runtime-testing:2.3.1")
+    docs("androidx.lifecycle:lifecycle-service:2.3.1")
+    docs("androidx.lifecycle:lifecycle-viewmodel:2.3.1")
     docs("androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha03")
-    docs("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0")
-    docs("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.0")
+    docs("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1")
+    docs("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.1")
     docs("androidx.loader:loader:1.1.0")
     docs("androidx.localbroadcastmanager:localbroadcastmanager:1.1.0-alpha01")
     docs("androidx.media2:media2-common:1.1.2")
diff --git a/docs/api_guidelines.md b/docs/api_guidelines.md
index 7905b89..88a2b26 100644
--- a/docs/api_guidelines.md
+++ b/docs/api_guidelines.md
@@ -1223,21 +1223,64 @@
     <tr>
         <td><code>RestrictTo.Scope</code></td>
         <td>Visibility by Maven coordinate</td>
+        <td>Versioning</td>
     </tr>
     <tr>
         <td><code>LIBRARY</code></td>
         <td><code>androidx.concurrent:concurrent</code></td>
+        <td>No compatibility gurantees (same as private)</td>
     </tr>
     <tr>
         <td><code>LIBRARY_GROUP</code></td>
         <td><code>androidx.concurrent:*</code></td>
+        <td>Semantic versioning (including deprecation)</td>
     </tr>
     <tr>
         <td><code>LIBRARY_GROUP_PREFIX</code></td>
         <td><code>androidx.*:*</code></td>
+        <td>Semantic versioning (including deprecation)</td>
     </tr>
 </table>
 
+#### `@IntDef` `@StringDef` and `@LongDef` and visibility
+
+All `@IntDef`, `@StringDef`, and `@LongDef` will be stripped from resulting
+artifacts to avoid issues where compiler inlining constants removes information
+as to which `@IntDef` defined the value of `1`. The annotations are extracted
+and packaged separately to be read by Android Studio and lint which enforces the
+types in application code.
+
+*   Libraries _must_ `@hide` all `@IntDef`, `@StringDef`, and `@LongDef`
+    declarations.
+*   Libraries _must_ expose constants used to define the `@IntDef` etc at the
+    same Java visibility as the hidden `@IntDef`
+*   Libraries _should_ also use @RestrictTo to create a warning when the type
+    used incorrectly.
+
+Here is a complete example of an `@IntDef`
+
+```java
+// constants match Java visibility of ExifStreamType
+// code outside this module interacting with ExifStreamType uses these constants
+public static final int STREAM_TYPE_FULL_IMAGE_DATA = 1;
+public static final int STREAM_TYPE_EXIF_DATA_ONLY = 2;
+
+/** @hide */
+@RestrictTo(RestrictTo.Scope.LIBRARY) // Don't export ExifStreamType outside module
+@Retention(RetentionPolicy.SOURCE)
+@IntDef({
+  STREAM_TYPE_FULL_IMAGE_DATA,
+  STREAM_TYPE_EXIF_DATA_ONLY,
+})
+public @interface ExifStreamType {}
+```
+
+Java visibilty should be set as appropriate for the code in question (`private`,
+`package` or `public`) and is unrelated to hiding.
+
+For more, read the section in
+[Android API Council Guidelines](https://android.googlesource.com/platform/developers/docs/+/refs/heads/master/api-guidelines/index.md#no-public-typedefs)
+
 ### Constructors {#constructors}
 
 #### View constructors {#view-constructors}
diff --git a/docs/kdoc_guidelines.md b/docs/kdoc_guidelines.md
new file mode 100644
index 0000000..c53ba8c
--- /dev/null
+++ b/docs/kdoc_guidelines.md
@@ -0,0 +1,176 @@
+# Kotlin documentation (KDoc) guidelines
+
+[TOC]
+
+This guide contains documentation guidance specific to Jetpack Kotlin APIs.
+General guidance from
+s.android.com/api-guidelines#docs
+should still be followed, this guide contains extra guidelines specifically for
+Kotlin documentation. For detailed information on KDoc's supported tags and
+syntax, see the
+[official documentation](https://kotlinlang.org/docs/kotlin-doc.html).
+
+## Guidelines for writing KDoc
+
+### Every parameter / property in a function / class should be documented
+
+Without an explicit `@param` / `@property` tag, a table entry for a particular
+parameter / property will not be generated. Make sure to add tags for *every*
+element to generate full documentation for an API.
+
+```kotlin {.good}
+/**
+ * ...
+ *
+ * @param1 ...
+ * @param2 ...
+ * @param3 ...
+ */
+fun foo(param1: Int, param2: String, param3: Boolean) {}
+```
+
+### Consider using all available tags to add documentation for elements
+
+Kotlin allows defining public APIs in a concise manner, which means that it can
+be easy to forget to write documentation for a specific element. For example,
+consider the following contrived example:
+
+```kotlin
+class Item<T>(val label: String, content: T)
+
+fun <T> Item<T>.appendContent(content: T): Item<T> { ... }
+```
+
+The class declaration contains:
+
+*   A generic type - `T`
+*   A property (that is also a constructor parameter) - `label`
+*   A constructor parameter - `content`
+*   A constructor function - `Item(label, content)`
+
+The function declaration contains:
+
+*   A generic type - `T`
+*   A receiver - `Item<T>`
+*   A parameter - `content`
+*   A return type - `Item<T>`
+
+When writing KDoc, consider adding documentation for each element:
+
+```kotlin {.good}
+/**
+ * An Item represents content inside a list...
+ *
+ * @param T the type of the content for this Item
+ * @property label optional label for this Item
+ * @param content the content for this Item
+ * @constructor creates a new Item
+ */
+class Item<T>(val label: String? = null, content: T)
+
+/**
+ * Appends [content] to [this] [Item], returning a new [Item].
+ *
+ * @param T the type of the content in this [Item]
+ * @receiver the [Item] to append [content] to
+ * @param content the [content] that will be appended to [this]
+ * @return a new [Item] representing [this] with [content] appended
+ */
+fun <T> Item<T>.appendContent(content: T): Item<T> { ... }
+```
+
+You may omit documentation for simple cases, such as a constructor for a data
+class that just sets properties and has no side effects, but in general it can
+be helpful to add documentation for all parts of an API.
+
+### Use `@sample` for each API that represents a standalone feature, or advanced behavior for a feature
+
+`@sample` allows you to reference a Kotlin function as sample code inside
+documentation. The body of the function will be added to the generated
+documentation inside a code block, allowing you to show usage for a particular
+API. Since this function is real Kotlin code that will be compiled and linted
+during the build, the sample will always be up to date with the API, reducing
+maintenance. You can use multiple samples per KDoc, with text in between
+explaining what the samples are showing. For more information on using
+`@sample`, see the
+[API guidelines](/company/teams/androidx/api_guidelines.md#sample-code-in-kotlin-modules).
+
+### Do not link to the same identifier inside documentation
+
+Avoid creating self-referential links:
+
+```kotlin {.bad}
+/**
+ * [Item] is ...
+ */
+class Item
+```
+
+These links are not actionable, as they will take the user to the same element
+they are already reading - instead refer to the element in the matching case
+without a link:
+
+```kotlin {.good}
+/**
+ * Item is ...
+ */
+class Item
+```
+
+## Javadoc - KDoc differences
+
+Some tags are shared between Javadoc and KDoc, such as `@param`, but there are
+notable differences between the syntax and tags supported. Unsupported syntax /
+tags do not currently show as an error in the IDE / during the build, so be
+careful to look out for the following important changes.
+
+### Hiding documentation
+
+Using `@suppress` will stop documentation being generated for a particular
+element. This is equivalent to using `@hide` in Android Javadoc.
+
+### Deprecation
+
+To mark an element as deprecated, use the `@Deprecated` annotation on the
+corresponding declaration, and consider including a `ReplaceWith` fragment to
+suggest the replacement for deprecated APIs.
+
+```kotlin {.good}
+package androidx.somepackage
+
+@Deprecated(
+    "Renamed to Bar",
+    replaceWith = ReplaceWith(
+        expression = "Bar",
+        // import(s) to be added
+        "androidx.somepackage.Bar"
+    )
+)
+class Foo
+
+class Bar
+```
+
+This is equivalent to using the `@deprecated` tag in Javadoc, but allows
+specifying more detailed deprecation messages, and different 'severity' levels
+of deprecation. For more information see the documentation for
+[@Deprecated](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-deprecated/).
+
+### Linking to elements
+
+To link to another element, put its name in square brackets. For example, to
+refer to the class `Foo`, use `[Foo]`. This is equivalent to `{@link Foo}` in
+Javadoc. You can also use a custom label, similar to Markdown: `[this
+class][Foo]`.
+
+### Code spans
+
+To mark some text as code, surround the text with a backtick (\`) as in
+Markdown. For example, \`true\`. This is equivalent to `{@code true}` in
+Javadoc.
+
+### Inline markup
+
+KDoc uses Markdown for inline markup, as opposed to Javadoc which uses HTML. The
+IDE / build will not show a warning if you use HTML tags such as `<p>`, so be
+careful not to accidentally use these in KDoc.
diff --git a/docs/onboarding.md b/docs/onboarding.md
index e0e72d9..ed9452b 100644
--- a/docs/onboarding.md
+++ b/docs/onboarding.md
@@ -217,6 +217,11 @@
 logs can be helpful for reporting issues to the team that manages our git
 servers.
 
+NOTE If `repo upload` or any `git` command hangs and causes your CPU usage to
+skyrocket (e.g. your laptop fan sounds like a jet engine), then you may be
+hitting a rare issue with Git-on-Borg and HTTP/2. You can force `git` and `repo`
+to use HTTP/1.1 with `git config --global http.version HTTP/1.1`.
+
 ## Building {#building}
 
 ### Modules and Maven artifacts {#modules-and-maven-artifacts}
@@ -228,11 +233,12 @@
 ./gradlew core:core:assemble
 ```
 
-Use the `-Pandroidx.allWarningsAsErrors` to make warnings fail your build (same
-as presubmits):
+To make warnings fail your build (same as presubmit), use the `--strict` flag,
+which our gradlew expands into a few correctness-related flags including
+`-Pandroidx.allWarningsAsErrors`:
 
 ```shell
-./gradlew core:core:assemble -Pandroidx.allWarningsAsErrors
+./gradlew core:core:assemble --strict
 ```
 
 To build every module, run the Lint verifier, verify the public API surface, and
@@ -243,11 +249,11 @@
 ./gradlew createArchive
 ```
 
-To run the complete build task that our build servers use, use the
-`buildOnServer` Gradle task:
+To run the complete build task that our build servers use, use the corresponding
+shell script:
 
 ```shell
-./gradlew buildOnServer
+./busytown/androidx.sh
 ```
 
 ### Attaching a debugger to the build
diff --git a/docs/principles.md b/docs/principles.md
index c498b10..6151a2c 100644
--- a/docs/principles.md
+++ b/docs/principles.md
@@ -58,8 +58,8 @@
 -   Avoid proprietary services or closed-source libraries for core
     functionality, and instead provide integration points that allow a developer
     to choose between a variety of services as the backing implementation
--   See [Integrating proprietary components] for guidance on using closed-source
-    and proprietary libraries and services
+-   See [Integrating proprietary components](open_source.md) for guidance on
+    using closed-source and proprietary libraries and services
 
 ### 6. Written using language-idiomatic APIs
 
diff --git a/docs/testability.md b/docs/testability.md
new file mode 100644
index 0000000..52d60a9
--- /dev/null
+++ b/docs/testability.md
@@ -0,0 +1,194 @@
+# Testability
+
+[TOC]
+
+## How to write testable libraries
+
+When developers use a Jetpack library, it should be easy to write reliable and
+automated tests for their own code's functionality. In most cases, tests written
+against a library will need to interact with that library in some way -- setting
+up preconditions, reaching synchronization points, making calls -- and the
+library should provide necessary functionality to enable such tests, either
+through public APIs or optional `-testing` artifacts.
+
+**Testability**, in this document, means how easily and effectively the users of
+a library can create tests for apps that use that library.
+
+NOTE Tests that check the behavior of a library have a different mission than
+tests made by app developers using that library; however, library developers may
+find themselves in a similar situation when writing tests for sample code.
+
+Often, the specifics of testability will vary from library to library and there
+is no one way of providing it. Some libraries have enough public API surface,
+others provide additional testing artifacts (e.g.
+[Lifecycle Runtime Testing artifact](https://maven.google.com/web/index.html?q=lifecycle#androidx.lifecycle:lifecycle-runtime-testing)).
+
+The best way to check if your library is testable is to try to write a sample
+app with tests. Unlike regular library tests, these apps will be limited to the
+public API surface of the library.
+
+Keep in mind that a good test for a sample app should have:
+
+*   [No side effects](#side-effects)
+*   [No dependencies on time / looper (except for UI)](#external-dependencies)
+*   [No private API calls](#private-apis)
+*   [No assumptions on undefined library behavior](#undefined-behavior)
+
+If you are able to write such tests for your library, you are good to go. If you
+struggled or found yourself writing duplicate testing code, there is room for
+improvement.
+
+To get started with sample code, see
+[Sample code in Kotlin modules](api_guidelines.md#sample-code-in-kotlin-modules)
+for information on writing samples that can be referenced from API reference
+documentation or [Project directory structure](policies.md#directory-structure)
+for module naming guidelines if you'd like to create a basic test app.
+
+### Avoiding side-effects {#side-effects}
+
+#### Ensure proper scoping for your library a.k.a. Avoid Singletons
+
+Singletons are usually bad for tests as they live across different tests,
+opening the gates for possible side-effects between tests. When possible, try to
+avoid using singletons. If it is not possible, consider providing a test
+artifact that will reset the state of the singleton between tests.
+
+#### Side effects due to external resources
+
+Sometimes, your library might be controlling resources on the device in which
+case even if it is not a singleton, it might have side-effects. For instance,
+Room, being a database library, inherently modifies a file on the disk. To allow
+proper isolated testing, Room provides a builder option to create the database
+[in memory](https://developer.android.com/reference/androidx/room/Room#inMemoryDatabaseBuilder\(android.content.Context,%20java.lang.Class%3CT%3E\))
+A possible alternative solution could be to provide a test rule that will
+properly close databases after each test.
+
+If your library needs an inherently singleton resource (e.g. `WorkManager` is a
+wrapper around `JobScheduler` and there is only 1 instance of it provided by the
+system), consider providing a testing artifact. To provide isolation for tests,
+the WorkManager library ships a
+[separate testing artifact](https://developer.android.com/topic/libraries/architecture/workmanager/how-to/integration-testing)
+
+### "External" dependencies {#external-dependencies}
+
+#### Allow configuration of external resource dependencies
+
+A common example of this use case is libraries that do multi-threaded
+operations. For Kotlin libraries, this is usually achieved by receiving a
+coroutine context or scope. For Java libraries, it is commonly an `Executor`. If
+you have a case like this, make sure it can be passed as a parameter to your
+library.
+
+NOTE Android API Guidelines require that methods accepting a callback
+[must also take an Executor](https://android.googlesource.com/platform/developers/docs/+/refs/heads/master/api-guidelines/index.md#callbacks-listener)
+
+For example, the Room library allows developers to
+[pass different executors](https://developer.android.com/reference/androidx/room/RoomDatabase.Builder#setQueryExecutor\(java.util.concurrent.Executor\))
+for background query operations. When writing a test, developers can invoke this
+with a custom executor where they can track work completion.
+
+*   [sample test](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt;l=672)
+
+If the external resource you require does not make sense as a public API, such
+as a main thread executor, then you can provide a testing artifact which will
+allow setting it. For example, the Lifecycle package depends on the main thread
+executor to function but for an application, customizing it does not make sense
+(as there is only 1 "pre-defined" main thread for an app). For testing purposes,
+the Lifecycle library provides a testing artifact which includes
+[TestRules](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:arch/core-testing/src/main/java/androidx/arch/core/executor/testing/CountingTaskExecutorRule.java)
+to change them.
+
+#### Fakes for external dependencies
+
+Sometimes, the developer might want to track side effects of your library for
+end to end testing. For instance, if your library provides some functionality
+that might decide to toggle bluetooth, outside developer's direct control, it
+might be a good idea to have an interface for that functionality and also
+provide a fake that can record such calls. If you don't think that interface
+makes sense as a library configuration, you can
+[restrict](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:annotation/annotation/src/main/java/androidx/annotation/RestrictTo.java)
+that interface to your library group and provide a testing artifacts with the
+fake so that developer can observe side effects only in tests while you can
+avoid creating unnecessary APIs.
+
+NOTE There is a fine balance between making a library configurable and making
+configuration a nightmare for the developer. You should try to always have
+defaults for these configurable objects to ensure your library is easy to use
+while still testable when necessary.
+
+### Avoiding the need for private API calls in tests {#private-apis}
+
+#### Provide additional functionality for tests
+
+There are certain situations where it could be useful to provide more APIs that
+only make sense in the scope of testing. For example, a `Lifecycle` class has
+methods that are bound to the main thread but developers may want to have other
+tests that rely on lifecycle but do not run on the main thread (or even on an
+emulator). For such cases, you may create APIs that are testing only to allow
+developers to use them only in tests while giving them the confidence that it
+will behave as close as possible to a real implementation. For the case above,
+`LifecycleRegistry` provides an API to
+[create](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:lifecycle/lifecycle-runtime/src/main/java/androidx/lifecycle/LifecycleRegistry.java;l=334)
+an instance of it that will not enforce thread restrictions.
+
+NOTE Even though the implementation referenced above is acceptable, it is always
+better to create such functionality in additional testing artifacts when
+possible.
+
+### Avoiding assumptions in app code for library behavior {#undefined-behavior}
+
+#### Provide fakes for common classes in a `-testing` artifact
+
+In some cases, the developer might need an instance of a class provided by your
+library but does not want to (or cannot) initiate it in tests. Moreover,
+behavior of such classes might not be fully defined for edge cases, making it
+difficult for developers to mock them.
+
+For such cases, it is a good practice to provide a fake implementation out of
+the box that can be controlled in tests. For example, the Lifecycle library
+provides a
+[fake implementation](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:lifecycle/lifecycle-runtime-testing/src/main/java/androidx/lifecycle/testing/TestLifecycleOwner.kt)
+for the `LifecycleOwner` class that can be manipulated in tests to create
+different use cases.
+
+## Document how to test with your library
+
+Even when your library is fully testable, it is often not clear for a developer
+to know which APIs are safe to call in tests and when. Providing guidance on
+[d.android.com](https://d.android.com) will make it much easier for the
+developer to start testing with your library.
+
+## Testability anti-patterns
+
+When writing tests against your APIs, look out for the following anti-patterns.
+
+### Calling `Instrumentation.waitForIdleSync()` as a synchronization barrier
+
+The `waitForIdle()` and `waitForIdleSync()` methods claim to "(Synchronously)
+wait for the application to be idle." and may seem like reasonable options when
+there is no obvious way to observe whether an action has completed; however,
+these methods know nothing about the context of the test and return when the
+main thread's message queue is empty.
+
+```java {.bad}
+view.requestKeyboardFocus();
+Instrumentation.waitForIdleSync();
+sendKeyEvents(view, "1234");
+// There is no guarantee that `view` has keyboard focus yet.
+device.pressEnter();
+```
+
+In apps with an active UI animation, the message queue is _never empty_. If the
+app is waiting for a callback across IPC, the message queue may be empty despite
+the test not reaching the desired state.
+
+In some cases, `waitForIdleSync()` introduces enough of a delay that unrelated
+asynchronous actions happen to have completed by the time the method returns;
+however, this delay is purely coincidental and eventually leads to flakiness.
+
+Instead, find a reliable synchronization barrier that guarantees the expected
+state has been reached or the requested action has been completed. This might
+mean adding listeners, callbacks, `ListenableFuture`s, or `LiveData`.
+
+See [Asynchronous work](api_guidelines.md#async) in the API Guidelines for more
+information on exposing the state of asynchronous work to clients.
diff --git a/docs/testing.md b/docs/testing.md
index f757002..0832b44 100644
--- a/docs/testing.md
+++ b/docs/testing.md
@@ -24,6 +24,10 @@
 and
 [`Parameterized`](https://junit.org/junit4/javadoc/4.12/org/junit/runners/Parameterized.html).
 
+NOTE For best practices on writing libraries in a way that makes it easy for end
+users -- and library developers -- to write tests, see the
+[Testability](testability.md) guide.
+
 ### What gets tested, and when
 
 We use the
diff --git a/emoji2/emoji2-benchmark/build.gradle b/emoji2/emoji2-benchmark/build.gradle
new file mode 100644
index 0000000..643376d
--- /dev/null
+++ b/emoji2/emoji2-benchmark/build.gradle
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 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.
+ */
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.Publish
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("kotlin-android")
+    id("androidx.benchmark")
+}
+
+dependencies {
+    androidTestImplementation(projectOrArtifact(":benchmark:benchmark-junit4"))
+    androidTestImplementation(JUNIT)
+    androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+    androidTestImplementation(ANDROIDX_TEST_CORE)
+    androidTestImplementation(ANDROIDX_TEST_RUNNER)
+    androidTestImplementation(ANDROIDX_TEST_RULES)
+    androidTestImplementation(KOTLIN_STDLIB)
+}
+
+androidx {
+    name = "Emoji2 Benchmarks"
+    publish = Publish.NONE
+    mavenGroup = LibraryGroups.NAVIGATION
+    inceptionYear = "2018"
+    description = "Navigation Benchmarks"
+}
diff --git a/benchmark/benchmark/lint-baseline.xml b/emoji2/emoji2-benchmark/lint-baseline.xml
similarity index 100%
rename from benchmark/benchmark/lint-baseline.xml
rename to emoji2/emoji2-benchmark/lint-baseline.xml
diff --git a/emoji2/emoji2-benchmark/src/androidTest/AndroidManifest.xml b/emoji2/emoji2-benchmark/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..07b3c28
--- /dev/null
+++ b/emoji2/emoji2-benchmark/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 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.
+  -->
+<manifest
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:tools="http://schemas.android.com/tools"
+        package="androidx.emoji2.benchmark">
+
+    <!-- Important: disable debuggable for accurate performance results -->
+    <application
+            android:debuggable="false"
+            tools:replace="android:debuggable">
+        <!-- enable profileableByShell for non-intrusive profiling tools -->
+        <!--suppress AndroidElementNotAllowed -->
+        <profileable android:shell="true"/>
+    </application>
+</manifest>
diff --git a/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/reflection/ReflectionImplementation.java b/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/reflection/ReflectionImplementation.java
new file mode 100644
index 0000000..c0b7a72
--- /dev/null
+++ b/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/reflection/ReflectionImplementation.java
@@ -0,0 +1,63 @@
+/*
+ * 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.emoji2.benchmark.reflection;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+
+public class ReflectionImplementation extends ReflectionParent {
+    @Override
+    public ReflectionParent actualCall() {
+        return super.actualCall();
+    }
+
+    ReflectionParent staticActualCall() throws Throwable {
+        return ParentMethodHandles.staticCall(this);
+    }
+
+    MethodHandle doMethodLookup() throws NoSuchMethodException, IllegalAccessException {
+        return ParentMethodHandles.doMethodLookup();
+    }
+
+    private static class ParentMethodHandles {
+        private static final MethodHandle sMethodHandle;
+
+        static {
+            MethodHandle sMethodHandle1 = null;
+            try {
+                sMethodHandle1 = doMethodLookup();
+            } catch (Throwable ignored) { }
+            sMethodHandle = sMethodHandle1;
+        }
+
+        private static MethodHandle doMethodLookup() throws IllegalAccessException,
+                NoSuchMethodException {
+            MethodHandles.Lookup lookup = MethodHandles.lookup().in(ReflectionParent.class);
+            Method method = ReflectionParent.class.getDeclaredMethod("actualCall");
+            method.setAccessible(true);
+            return lookup.unreflectSpecial(method, ReflectionParent.class);
+        }
+
+        public static ReflectionParent staticCall(ReflectionImplementation reflectionImplementation)
+                throws Throwable {
+            return (ReflectionParent) sMethodHandle.bindTo(reflectionImplementation)
+                    .invokeWithArguments();
+        }
+    }
+
+}
diff --git a/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/reflection/ReflectionParent.java b/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/reflection/ReflectionParent.java
new file mode 100644
index 0000000..d297463
--- /dev/null
+++ b/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/reflection/ReflectionParent.java
@@ -0,0 +1,23 @@
+/*
+ * 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.emoji2.benchmark.reflection;
+
+public class ReflectionParent {
+    public ReflectionParent actualCall() {
+        return this;
+    }
+}
diff --git a/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/reflection/ReflectionsBenchmark.java b/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/reflection/ReflectionsBenchmark.java
new file mode 100644
index 0000000..c9e59fa
--- /dev/null
+++ b/emoji2/emoji2-benchmark/src/androidTest/java/androidx/emoji2/benchmark/reflection/ReflectionsBenchmark.java
@@ -0,0 +1,77 @@
+/*
+ * 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.emoji2.benchmark.reflection;
+
+import static org.junit.Assert.assertNotNull;
+
+import androidx.benchmark.BenchmarkState;
+import androidx.benchmark.junit4.BenchmarkRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.invoke.MethodHandle;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class ReflectionsBenchmark {
+    @Rule
+    public BenchmarkRule benchmarkRule = new BenchmarkRule();
+
+    @Test
+    public void static_methodHandle() throws Throwable {
+        BenchmarkState state = benchmarkRule.getState();
+        ReflectionImplementation subject = new ReflectionImplementation();
+        ReflectionParent result = null;
+        while (state.keepRunning()) {
+            result = subject.staticActualCall();
+        }
+        assertNotNull(result);
+    }
+
+    @Test
+    public void regularJavaDispatch() {
+        BenchmarkState state = benchmarkRule.getState();
+        ReflectionImplementation subject = new ReflectionImplementation();
+        ReflectionParent result = null;
+        while (state.keepRunning()) {
+            result = subject.actualCall();
+        }
+        assertNotNull(result);
+    }
+
+    /**
+     * This test is not an accurate reflection of first lookup cost, as it will warm up caches
+     * before the main benchmark starts.
+     *
+     * However, it is a good _lower bound_ of the cost to do this lookup, with the assumption
+     * that real world lookups will always be the same cost or slower.
+     */
+    @Test
+    public void doWarmedUpMethodLookup() throws NoSuchMethodException, IllegalAccessException {
+        BenchmarkState state = benchmarkRule.getState();
+        ReflectionImplementation subject = new ReflectionImplementation();
+        MethodHandle result = null;
+        while (state.keepRunning()) {
+            result = subject.doMethodLookup();
+        }
+        assertNotNull(result);
+    }
+}
diff --git a/emoji2/emoji2-benchmark/src/main/AndroidManifest.xml b/emoji2/emoji2-benchmark/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a5a978e
--- /dev/null
+++ b/emoji2/emoji2-benchmark/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<!--
+  ~ Copyright (C) 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.
+  -->
+
+<manifest package="androidx.emoji2.benchmark" />
diff --git a/emoji2/emoji2-views-core/AndroidManifest.xml b/emoji2/emoji2-views-helper/AndroidManifest.xml
similarity index 100%
rename from emoji2/emoji2-views-core/AndroidManifest.xml
rename to emoji2/emoji2-views-helper/AndroidManifest.xml
diff --git a/emoji2/emoji2-views-core/build.gradle b/emoji2/emoji2-views-helper/build.gradle
similarity index 100%
rename from emoji2/emoji2-views-core/build.gradle
rename to emoji2/emoji2-views-helper/build.gradle
diff --git a/emoji2/emoji2-views-core/src/androidTest/AndroidManifest.xml b/emoji2/emoji2-views-helper/src/androidTest/AndroidManifest.xml
similarity index 100%
rename from emoji2/emoji2-views-core/src/androidTest/AndroidManifest.xml
rename to emoji2/emoji2-views-helper/src/androidTest/AndroidManifest.xml
diff --git a/emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/helpers/EmojiEditTextHelperPre19Test.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/helpers/EmojiEditTextHelperPre19Test.java
similarity index 100%
rename from emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/helpers/EmojiEditTextHelperPre19Test.java
rename to emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/helpers/EmojiEditTextHelperPre19Test.java
diff --git a/emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/helpers/EmojiEditTextHelperTest.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/helpers/EmojiEditTextHelperTest.java
similarity index 100%
rename from emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/helpers/EmojiEditTextHelperTest.java
rename to emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/helpers/EmojiEditTextHelperTest.java
diff --git a/emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/helpers/EmojiEditableFactoryTest.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/helpers/EmojiEditableFactoryTest.java
similarity index 100%
rename from emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/helpers/EmojiEditableFactoryTest.java
rename to emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/helpers/EmojiEditableFactoryTest.java
diff --git a/emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/helpers/EmojiInputConnectionTest.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/helpers/EmojiInputConnectionTest.java
similarity index 100%
rename from emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/helpers/EmojiInputConnectionTest.java
rename to emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/helpers/EmojiInputConnectionTest.java
diff --git a/emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/helpers/EmojiInputFilterTest.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/helpers/EmojiInputFilterTest.java
similarity index 100%
rename from emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/helpers/EmojiInputFilterTest.java
rename to emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/helpers/EmojiInputFilterTest.java
diff --git a/emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/helpers/EmojiKeyListenerTest.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/helpers/EmojiKeyListenerTest.java
similarity index 100%
rename from emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/helpers/EmojiKeyListenerTest.java
rename to emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/helpers/EmojiKeyListenerTest.java
diff --git a/emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/helpers/EmojiTextViewHelperPre19Test.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/helpers/EmojiTextViewHelperPre19Test.java
similarity index 100%
rename from emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/helpers/EmojiTextViewHelperPre19Test.java
rename to emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/helpers/EmojiTextViewHelperPre19Test.java
diff --git a/emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/helpers/EmojiTextViewHelperTest.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/helpers/EmojiTextViewHelperTest.java
similarity index 100%
rename from emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/helpers/EmojiTextViewHelperTest.java
rename to emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/helpers/EmojiTextViewHelperTest.java
diff --git a/emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/helpers/EmojiTextWatcherTest.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/helpers/EmojiTextWatcherTest.java
similarity index 100%
rename from emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/helpers/EmojiTextWatcherTest.java
rename to emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/helpers/EmojiTextWatcherTest.java
diff --git a/emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/helpers/EmojiTransformationMethodTest.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/helpers/EmojiTransformationMethodTest.java
similarity index 100%
rename from emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/helpers/EmojiTransformationMethodTest.java
rename to emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/helpers/EmojiTransformationMethodTest.java
diff --git a/emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/util/Emoji.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/util/Emoji.java
similarity index 100%
rename from emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/util/Emoji.java
rename to emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/util/Emoji.java
diff --git a/emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/util/EmojiMatcher.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/util/EmojiMatcher.java
similarity index 100%
rename from emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/util/EmojiMatcher.java
rename to emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/util/EmojiMatcher.java
diff --git a/emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/util/KeyboardUtil.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/util/KeyboardUtil.java
similarity index 100%
rename from emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/util/KeyboardUtil.java
rename to emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/util/KeyboardUtil.java
diff --git a/emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/util/TestString.java b/emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/util/TestString.java
similarity index 100%
rename from emoji2/emoji2-views-core/src/androidTest/java/androidx/emoji2/util/TestString.java
rename to emoji2/emoji2-views-helper/src/androidTest/java/androidx/emoji2/util/TestString.java
diff --git a/emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiEditTextHelper.java b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiEditTextHelper.java
similarity index 98%
rename from emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiEditTextHelper.java
rename to emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiEditTextHelper.java
index 75ab24e..54216ea 100644
--- a/emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiEditTextHelper.java
+++ b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiEditTextHelper.java
@@ -15,8 +15,6 @@
  */
 package androidx.emoji2.helpers;
 
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-
 import android.os.Build;
 import android.text.method.KeyListener;
 import android.view.inputmethod.EditorInfo;
@@ -159,7 +157,7 @@
      *
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public void setEmojiReplaceStrategy(@EmojiCompat.ReplaceStrategy int replaceStrategy) {
         mEmojiReplaceStrategy = replaceStrategy;
         mHelper.setEmojiReplaceStrategy(replaceStrategy);
@@ -174,7 +172,7 @@
      *                        {@link EmojiCompat#REPLACE_STRATEGY_ALL}
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public int getEmojiReplaceStrategy() {
         return mEmojiReplaceStrategy;
     }
diff --git a/emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiEditableFactory.java b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiEditableFactory.java
similarity index 100%
rename from emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiEditableFactory.java
rename to emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiEditableFactory.java
diff --git a/emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiInputConnection.java b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiInputConnection.java
similarity index 97%
rename from emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiInputConnection.java
rename to emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiInputConnection.java
index 91dcb46..03f41ce 100644
--- a/emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiInputConnection.java
+++ b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiInputConnection.java
@@ -15,8 +15,6 @@
  */
 package androidx.emoji2.helpers;
 
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-
 import android.text.Editable;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
@@ -38,7 +36,7 @@
  *
  * @hide
  */
-@RestrictTo(LIBRARY_GROUP_PREFIX)
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
 final class EmojiInputConnection extends InputConnectionWrapper {
     private final TextView mTextView;
diff --git a/emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiInputFilter.java b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiInputFilter.java
similarity index 97%
rename from emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiInputFilter.java
rename to emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiInputFilter.java
index ccb3033..6560a80 100644
--- a/emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiInputFilter.java
+++ b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiInputFilter.java
@@ -15,8 +15,6 @@
  */
 package androidx.emoji2.helpers;
 
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-
 import android.text.Selection;
 import android.text.Spannable;
 import android.text.Spanned;
@@ -40,7 +38,7 @@
  *
  * @hide
  */
-@RestrictTo(LIBRARY_GROUP_PREFIX)
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
 final class EmojiInputFilter implements android.text.InputFilter {
     private final TextView mTextView;
diff --git a/emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiKeyListener.java b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiKeyListener.java
similarity index 96%
rename from emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiKeyListener.java
rename to emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiKeyListener.java
index a5b2129..e3d0a72 100644
--- a/emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiKeyListener.java
+++ b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiKeyListener.java
@@ -15,8 +15,6 @@
  */
 package androidx.emoji2.helpers;
 
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-
 import android.text.Editable;
 import android.text.method.KeyListener;
 import android.view.KeyEvent;
@@ -32,7 +30,7 @@
  *
  * @hide
  */
-@RestrictTo(LIBRARY_GROUP_PREFIX)
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
 final class EmojiKeyListener implements android.text.method.KeyListener {
     private final android.text.method.KeyListener mKeyListener;
diff --git a/emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiTextViewHelper.java b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiTextViewHelper.java
similarity index 100%
rename from emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiTextViewHelper.java
rename to emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiTextViewHelper.java
diff --git a/emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiTextWatcher.java b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiTextWatcher.java
similarity index 97%
rename from emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiTextWatcher.java
rename to emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiTextWatcher.java
index 43887bb..c86f3e2 100644
--- a/emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiTextWatcher.java
+++ b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiTextWatcher.java
@@ -15,8 +15,6 @@
  */
 package androidx.emoji2.helpers;
 
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-
 import android.text.Editable;
 import android.text.Selection;
 import android.text.Spannable;
@@ -36,7 +34,7 @@
  *
  * @hide
  */
-@RestrictTo(LIBRARY_GROUP_PREFIX)
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
 final class EmojiTextWatcher implements android.text.TextWatcher {
     private final EditText mEditText;
diff --git a/emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiTransformationMethod.java b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiTransformationMethod.java
similarity index 95%
rename from emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiTransformationMethod.java
rename to emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiTransformationMethod.java
index 78756b6..47484e5 100644
--- a/emoji2/emoji2-views-core/src/main/java/androidx/emoji2/helpers/EmojiTransformationMethod.java
+++ b/emoji2/emoji2-views-helper/src/main/java/androidx/emoji2/helpers/EmojiTransformationMethod.java
@@ -15,8 +15,6 @@
  */
 package androidx.emoji2.helpers;
 
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-
 import android.graphics.Rect;
 import android.text.method.TransformationMethod;
 import android.view.View;
@@ -32,7 +30,7 @@
  *
  * @hide
  */
-@RestrictTo(LIBRARY_GROUP_PREFIX)
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 @RequiresApi(19)
 class EmojiTransformationMethod implements TransformationMethod {
     private final TransformationMethod mTransformationMethod;
diff --git a/emoji2/emoji2-views/build.gradle b/emoji2/emoji2-views/build.gradle
index 6876fb1..9fac04c 100644
--- a/emoji2/emoji2-views/build.gradle
+++ b/emoji2/emoji2-views/build.gradle
@@ -13,7 +13,7 @@
 dependencies {
     api("androidx.core:core:1.3.0-rc01")
     api(project(":emoji2:emoji2"))
-    implementation(project(":emoji2:emoji2-views-core"))
+    implementation(project(":emoji2:emoji2-views-helper"))
 
     implementation("androidx.collection:collection:1.1.0")
 
diff --git a/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/EmojiButton.java b/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/EmojiButton.java
index 1b20ab5..08d5465 100644
--- a/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/EmojiButton.java
+++ b/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/EmojiButton.java
@@ -15,9 +15,7 @@
  */
 package androidx.emoji2.widget;
 
-import android.annotation.SuppressLint;
 import android.content.Context;
-import android.os.Build;
 import android.text.InputFilter;
 import android.util.AttributeSet;
 import android.view.ActionMode;
@@ -25,7 +23,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 import androidx.core.widget.TextViewCompat;
 import androidx.emoji2.helpers.EmojiTextViewHelper;
 
@@ -57,14 +54,6 @@
         init();
     }
 
-    @SuppressLint("UnsafeNewApiCall")
-    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
-    public EmojiButton(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        init();
-    }
-
     private void init() {
         if (!mInitialized) {
             mInitialized = true;
diff --git a/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/EmojiEditText.java b/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/EmojiEditText.java
index 1dae701..b0925a6 100644
--- a/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/EmojiEditText.java
+++ b/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/EmojiEditText.java
@@ -15,9 +15,7 @@
  */
 package androidx.emoji2.widget;
 
-import android.annotation.SuppressLint;
 import android.content.Context;
-import android.os.Build;
 import android.text.method.KeyListener;
 import android.util.AttributeSet;
 import android.view.ActionMode;
@@ -28,7 +26,6 @@
 import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 import androidx.core.widget.TextViewCompat;
 import androidx.emoji2.helpers.EmojiEditTextHelper;
 import androidx.emoji2.text.EmojiCompat;
@@ -63,14 +60,6 @@
         init(attrs, defStyleAttr, 0 /*defStyleRes*/);
     }
 
-    @SuppressLint("UnsafeNewApiCall")
-    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
-    public EmojiEditText(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        init(attrs, defStyleAttr, defStyleRes);
-    }
-
     private void init(@Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         if (!mInitialized) {
             mInitialized = true;
diff --git a/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/EmojiExtractEditText.java b/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/EmojiExtractEditText.java
index 95df225..fb7443c 100644
--- a/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/EmojiExtractEditText.java
+++ b/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/EmojiExtractEditText.java
@@ -16,12 +16,8 @@
 
 package androidx.emoji2.widget;
 
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-
-import android.annotation.SuppressLint;
 import android.content.Context;
 import android.inputmethodservice.ExtractEditText;
-import android.os.Build;
 import android.text.method.KeyListener;
 import android.util.AttributeSet;
 import android.view.ActionMode;
@@ -32,7 +28,6 @@
 import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.core.widget.TextViewCompat;
 import androidx.emoji2.helpers.EmojiEditTextHelper;
@@ -46,7 +41,7 @@
  *
  * @hide
  */
-@RestrictTo(LIBRARY_GROUP_PREFIX)
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 public class EmojiExtractEditText extends ExtractEditText {
     private EmojiEditTextHelper mEmojiEditTextHelper;
 
@@ -72,14 +67,6 @@
         init(attrs, defStyleAttr, 0 /*defStyleRes*/);
     }
 
-    @SuppressLint("UnsafeNewApiCall")
-    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
-    public EmojiExtractEditText(@NonNull Context context, @Nullable AttributeSet attrs,
-            int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        init(attrs, defStyleAttr, defStyleRes);
-    }
-
     private void init(@Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         if (!mInitialized) {
             mInitialized = true;
diff --git a/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/EmojiExtractTextLayout.java b/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/EmojiExtractTextLayout.java
index c930389..a0a0ea6 100644
--- a/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/EmojiExtractTextLayout.java
+++ b/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/EmojiExtractTextLayout.java
@@ -16,11 +16,9 @@
 
 package androidx.emoji2.widget;
 
-import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.inputmethodservice.InputMethodService;
-import android.os.Build;
 import android.text.InputType;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -32,7 +30,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 import androidx.core.view.ViewCompat;
 import androidx.emoji2.text.EmojiCompat;
 import androidx.emoji2.text.EmojiSpan;
@@ -93,14 +90,6 @@
         init(context, attrs, defStyleAttr, 0 /*defStyleRes*/);
     }
 
-    @SuppressLint("UnsafeNewApiCall")
-    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
-    public EmojiExtractTextLayout(@NonNull Context context, @Nullable AttributeSet attrs,
-            int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        init(context, attrs, defStyleAttr, defStyleRes);
-    }
-
     private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         if (!mInitialized) {
diff --git a/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/EmojiTextView.java b/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/EmojiTextView.java
index 709ddd3..137deb2 100644
--- a/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/EmojiTextView.java
+++ b/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/EmojiTextView.java
@@ -15,9 +15,7 @@
  */
 package androidx.emoji2.widget;
 
-import android.annotation.SuppressLint;
 import android.content.Context;
-import android.os.Build;
 import android.text.InputFilter;
 import android.util.AttributeSet;
 import android.view.ActionMode;
@@ -25,7 +23,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 import androidx.core.widget.TextViewCompat;
 import androidx.emoji2.helpers.EmojiTextViewHelper;
 
@@ -57,14 +54,6 @@
         init();
     }
 
-    @SuppressLint("UnsafeNewApiCall")
-    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
-    public EmojiTextView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        init();
-    }
-
     private void init() {
         if (!mInitialized) {
             mInitialized = true;
diff --git a/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/ExtractButtonCompat.java b/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/ExtractButtonCompat.java
index cf839e0..c542b5f 100644
--- a/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/ExtractButtonCompat.java
+++ b/emoji2/emoji2-views/src/main/java/androidx/emoji2/widget/ExtractButtonCompat.java
@@ -16,18 +16,13 @@
 
 package androidx.emoji2.widget;
 
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-
-import android.annotation.SuppressLint;
 import android.content.Context;
-import android.os.Build;
 import android.util.AttributeSet;
 import android.view.ActionMode;
 import android.widget.Button;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.core.widget.TextViewCompat;
 
@@ -36,7 +31,7 @@
  * inflating {@link EmojiExtractEditText} for keyboard use.
  * @hide
  */
-@RestrictTo(LIBRARY_GROUP_PREFIX)
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 public class ExtractButtonCompat extends Button {
     public ExtractButtonCompat(@NonNull Context context) {
         super(context, null);
@@ -51,13 +46,6 @@
         super(context, attrs, defStyleAttr);
     }
 
-    @SuppressLint("UnsafeNewApiCall")
-    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
-    public ExtractButtonCompat(@NonNull Context context, @Nullable AttributeSet attrs,
-            int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
     /**
      * Pretend like the window this view is in always has focus, so it will
      * highlight when selected.
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompat.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompat.java
index 86c3588..f13c2a9 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompat.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiCompat.java
@@ -15,7 +15,9 @@
  */
 package androidx.emoji2.text;
 
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static androidx.annotation.RestrictTo.Scope.TESTS;
 
 import android.graphics.Color;
 import android.graphics.Paint;
@@ -38,7 +40,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
-import androidx.annotation.VisibleForTesting;
 import androidx.collection.ArraySet;
 import androidx.core.util.Preconditions;
 
@@ -130,10 +131,10 @@
     /**
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(LIBRARY)
     @IntDef({LOAD_STATE_DEFAULT, LOAD_STATE_LOADING, LOAD_STATE_SUCCEEDED, LOAD_STATE_FAILED})
     @Retention(RetentionPolicy.SOURCE)
-    public @interface LoadState {
+    private @interface LoadState {
     }
 
     /**
@@ -158,7 +159,7 @@
     /**
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(LIBRARY)
     @IntDef({REPLACE_STRATEGY_DEFAULT, REPLACE_STRATEGY_NON_EXISTENT, REPLACE_STRATEGY_ALL})
     @Retention(RetentionPolicy.SOURCE)
     public @interface ReplaceStrategy {
@@ -182,7 +183,7 @@
     /**
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(LIBRARY)
     @IntDef({LOAD_STRATEGY_DEFAULT, LOAD_STRATEGY_MANUAL})
     @Retention(RetentionPolicy.SOURCE)
     public @interface LoadStrategy {
@@ -191,7 +192,7 @@
     /**
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(LIBRARY)
     static final int EMOJI_COUNT_UNLIMITED = Integer.MAX_VALUE;
 
     private static final Object INSTANCE_LOCK = new Object();
@@ -322,8 +323,7 @@
      * @hide
      */
     @SuppressWarnings("GuardedBy")
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
-    @VisibleForTesting
+    @RestrictTo(TESTS)
     @NonNull
     public static EmojiCompat reset(@NonNull final Config config) {
         synchronized (INSTANCE_LOCK) {
@@ -339,8 +339,7 @@
      * @hide
      */
     @SuppressWarnings("GuardedBy")
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
-    @VisibleForTesting
+    @RestrictTo(TESTS)
     @Nullable
     public static EmojiCompat reset(@Nullable final EmojiCompat emojiCompat) {
         synchronized (INSTANCE_LOCK) {
@@ -513,7 +512,7 @@
      * @return whether a background should be drawn for the emoji.
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(LIBRARY)
     boolean isEmojiSpanIndicatorEnabled() {
         return mEmojiSpanIndicatorEnabled;
     }
@@ -522,7 +521,7 @@
      * @return whether a background should be drawn for the emoji.
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(LIBRARY)
     @ColorInt int getEmojiSpanIndicatorColor() {
         return mEmojiSpanIndicatorColor;
     }
@@ -818,7 +817,7 @@
      *
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(LIBRARY_GROUP)
     public void updateEditorInfoAttrs(@NonNull final EditorInfo outAttrs) {
         //noinspection ConstantConditions
         if (isInitialized() && outAttrs != null && outAttrs.extras != null) {
@@ -831,7 +830,7 @@
      *
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(LIBRARY)
     @RequiresApi(19)
     static class SpanFactory {
         /**
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiMetadata.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiMetadata.java
index d6ef218..8f15f52 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiMetadata.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiMetadata.java
@@ -15,7 +15,7 @@
  */
 package androidx.emoji2.text;
 
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
 import android.annotation.SuppressLint;
 import android.graphics.Canvas;
@@ -39,7 +39,7 @@
  *
  * @hide
  */
-@RestrictTo(LIBRARY_GROUP_PREFIX)
+@RestrictTo(LIBRARY_GROUP)
 @AnyThread
 @RequiresApi(19)
 public class EmojiMetadata {
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiProcessor.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiProcessor.java
index 98849eb..8556df3 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiProcessor.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiProcessor.java
@@ -15,7 +15,7 @@
  */
 package androidx.emoji2.text;
 
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
 
 import android.os.Build;
 import android.text.Editable;
@@ -49,7 +49,7 @@
  * @hide
  */
 @AnyThread
-@RestrictTo(LIBRARY_GROUP_PREFIX)
+@RestrictTo(LIBRARY)
 @RequiresApi(19)
 final class EmojiProcessor {
 
@@ -782,7 +782,7 @@
      * @hide
      */
     @AnyThread
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(LIBRARY)
     public static class DefaultGlyphChecker implements EmojiCompat.GlyphChecker {
         /**
          * Default text size for {@link #mTextPaint}.
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiSpan.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiSpan.java
index dd07ff1..5760f20 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiSpan.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/EmojiSpan.java
@@ -15,7 +15,8 @@
  */
 package androidx.emoji2.text;
 
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+import static androidx.annotation.RestrictTo.Scope.TESTS;
 
 import android.annotation.SuppressLint;
 import android.graphics.Paint;
@@ -25,7 +26,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
-import androidx.annotation.VisibleForTesting;
 import androidx.core.util.Preconditions;
 
 /**
@@ -70,7 +70,7 @@
      *
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(LIBRARY)
     EmojiSpan(@NonNull final EmojiMetadata metadata) {
         Preconditions.checkNotNull(metadata, "metadata cannot be null");
         mMetadata = metadata;
@@ -102,7 +102,7 @@
     /**
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(LIBRARY)
     final EmojiMetadata getMetadata() {
         return mMetadata;
     }
@@ -112,7 +112,7 @@
      *
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(LIBRARY)
     final int getWidth() {
         return mWidth;
     }
@@ -122,7 +122,7 @@
      *
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(LIBRARY)
     final int getHeight() {
         return mHeight;
     }
@@ -130,7 +130,7 @@
     /**
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(LIBRARY)
     final float getRatio() {
         return mRatio;
     }
@@ -140,8 +140,7 @@
      *
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
-    @VisibleForTesting
+    @RestrictTo(TESTS)
     public final int getId() {
         return getMetadata().getId();
     }
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/FontRequestEmojiCompatConfig.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/FontRequestEmojiCompatConfig.java
index f20049c..1e3006e 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/FontRequestEmojiCompatConfig.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/FontRequestEmojiCompatConfig.java
@@ -123,7 +123,7 @@
     /**
      * @hide
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public FontRequestEmojiCompatConfig(@NonNull Context context, @NonNull FontRequest request,
             @NonNull FontProviderHelper fontProviderHelper) {
         super(new FontRequestMetadataLoader(context, request, fontProviderHelper));
@@ -331,7 +331,7 @@
      * Delegate class for mocking FontsContractCompat.fetchFonts.
      * @hide
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public static class FontProviderHelper {
         /** Calls FontsContractCompat.fetchFonts. */
         @NonNull
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/MetadataListReader.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/MetadataListReader.java
index 6db7379..674202d 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/MetadataListReader.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/MetadataListReader.java
@@ -15,8 +15,6 @@
  */
 package androidx.emoji2.text;
 
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-
 import android.content.res.AssetManager;
 
 import androidx.annotation.AnyThread;
@@ -36,7 +34,7 @@
  *
  * @hide
  */
-@RestrictTo(LIBRARY_GROUP_PREFIX)
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 @AnyThread
 @RequiresApi(19)
 class MetadataListReader {
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/MetadataRepo.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/MetadataRepo.java
index 00fef53..3549952 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/MetadataRepo.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/MetadataRepo.java
@@ -15,8 +15,6 @@
  */
 package androidx.emoji2.text;
 
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-
 import android.content.res.AssetManager;
 import android.graphics.Typeface;
 import android.util.SparseArray;
@@ -148,7 +146,7 @@
      * @hide
      */
     @NonNull
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     Typeface getTypeface() {
         return mTypeface;
     }
@@ -156,7 +154,7 @@
     /**
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     int getMetadataVersion() {
         return mMetadataList.version();
     }
@@ -165,7 +163,7 @@
      * @hide
      */
     @NonNull
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     Node getRootNode() {
         return mRootNode;
     }
@@ -174,7 +172,7 @@
      * @hide
      */
     @NonNull
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public char[] getEmojiCharArray() {
         return mEmojiCharArray;
     }
@@ -183,7 +181,7 @@
      * @hide
      */
     @NonNull
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public MetadataList getMetadataList() {
         return mMetadataList;
     }
@@ -193,7 +191,7 @@
      *
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     @VisibleForTesting
     void put(@NonNull final EmojiMetadata data) {
         Preconditions.checkNotNull(data, "emoji metadata cannot be null");
@@ -209,7 +207,7 @@
      *
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     static class Node {
         private final SparseArray<Node> mChildren;
         private EmojiMetadata mData;
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/SpannableBuilder.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/SpannableBuilder.java
index 01399a4..8eede15 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/SpannableBuilder.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/SpannableBuilder.java
@@ -15,8 +15,6 @@
  */
 package androidx.emoji2.text;
 
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-
 import android.annotation.SuppressLint;
 import android.text.Editable;
 import android.text.SpanWatcher;
@@ -47,7 +45,7 @@
  *
  * @hide
  */
-@RestrictTo(LIBRARY_GROUP_PREFIX)
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public final class SpannableBuilder extends SpannableStringBuilder {
     /**
      * DynamicLayout$ChangeWatcher class.
@@ -62,7 +60,7 @@
     /**
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     SpannableBuilder(@NonNull Class<?> watcherClass) {
         Preconditions.checkNotNull(watcherClass, "watcherClass cannot be null");
         mWatcherClass = watcherClass;
@@ -71,7 +69,7 @@
     /**
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     SpannableBuilder(@NonNull Class<?> watcherClass, @NonNull CharSequence text) {
         super(text);
         Preconditions.checkNotNull(watcherClass, "watcherClass cannot be null");
@@ -81,7 +79,7 @@
     /**
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     SpannableBuilder(@NonNull Class<?> watcherClass, @NonNull CharSequence text, int start,
             int end) {
         super(text, start, end);
@@ -93,7 +91,7 @@
      * @hide
      */
     @NonNull
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public static SpannableBuilder create(@NonNull Class<?> clazz, @NonNull CharSequence text) {
         return new SpannableBuilder(clazz, text);
     }
@@ -257,7 +255,7 @@
     /**
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public void beginBatchEdit() {
         blockWatchers();
     }
@@ -265,7 +263,7 @@
     /**
      * @hide
      */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     public void endBatchEdit() {
         unblockwatchers();
         fireWatchers();
diff --git a/emoji2/emoji2/src/main/java/androidx/emoji2/text/TypefaceEmojiSpan.java b/emoji2/emoji2/src/main/java/androidx/emoji2/text/TypefaceEmojiSpan.java
index 5eb3ff9..f0cd9fa 100644
--- a/emoji2/emoji2/src/main/java/androidx/emoji2/text/TypefaceEmojiSpan.java
+++ b/emoji2/emoji2/src/main/java/androidx/emoji2/text/TypefaceEmojiSpan.java
@@ -15,8 +15,6 @@
  */
 package androidx.emoji2.text;
 
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-
 import android.annotation.SuppressLint;
 import android.graphics.Canvas;
 import android.graphics.Paint;
@@ -33,7 +31,7 @@
  *
  * @hide
  */
-@RestrictTo(LIBRARY_GROUP_PREFIX)
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 @RequiresApi(19)
 public final class TypefaceEmojiSpan extends EmojiSpan {
 
diff --git a/fragment/OWNERS b/fragment/OWNERS
index f66b82d..f35d2c7 100644
--- a/fragment/OWNERS
+++ b/fragment/OWNERS
@@ -1,3 +1,6 @@
 [email protected]
 [email protected]
[email protected]
\ No newline at end of file
[email protected]
+
+per-file settings.gradle = [email protected], [email protected]
+
diff --git a/fragment/fragment-ktx/build.gradle b/fragment/fragment-ktx/build.gradle
index fd891e4..cd841ce 100644
--- a/fragment/fragment-ktx/build.gradle
+++ b/fragment/fragment-ktx/build.gradle
@@ -27,7 +27,7 @@
 
 dependencies {
     api(project(":fragment:fragment"))
-    api("androidx.activity:activity-ktx:1.2.0") {
+    api("androidx.activity:activity-ktx:1.2.2") {
         because "Mirror fragment dependency graph for -ktx artifacts"
     }
     api("androidx.core:core-ktx:1.1.0") {
@@ -36,10 +36,10 @@
     api("androidx.collection:collection-ktx:1.1.0") {
         because "Mirror fragment dependency graph for -ktx artifacts"
     }
-    api("androidx.lifecycle:lifecycle-livedata-core-ktx:2.3.0") {
+    api("androidx.lifecycle:lifecycle-livedata-core-ktx:2.3.1") {
         because 'Mirror fragment dependency graph for -ktx artifacts'
     }
-    api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0")
+    api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1")
     api("androidx.savedstate:savedstate-ktx:1.1.0") {
         because 'Mirror fragment dependency graph for -ktx artifacts'
     }
diff --git a/fragment/fragment/api/public_plus_experimental_current.txt b/fragment/fragment/api/public_plus_experimental_current.txt
index 97a49c7..39da942 100644
--- a/fragment/fragment/api/public_plus_experimental_current.txt
+++ b/fragment/fragment/api/public_plus_experimental_current.txt
@@ -461,6 +461,7 @@
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public final class FragmentStrictMode {
     method public static androidx.fragment.app.strictmode.FragmentStrictMode.Policy getDefaultPolicy();
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public static void onSetUserVisibleHint(androidx.fragment.app.Fragment);
     method public static void setDefaultPolicy(androidx.fragment.app.strictmode.FragmentStrictMode.Policy);
   }
 
@@ -475,11 +476,16 @@
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public static final class FragmentStrictMode.Policy.Builder {
     ctor public FragmentStrictMode.Policy.Builder();
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy build();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectSetUserVisibleHint();
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyDeath();
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyListener(androidx.fragment.app.strictmode.FragmentStrictMode.OnViolationListener);
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyLog();
   }
 
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public final class SetUserVisibleHintViolation extends androidx.fragment.app.strictmode.Violation {
+    ctor public SetUserVisibleHintViolation();
+  }
+
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public abstract class Violation extends java.lang.RuntimeException {
     ctor public Violation();
   }
diff --git a/fragment/fragment/api/restricted_current.txt b/fragment/fragment/api/restricted_current.txt
index ab90399..889d15a 100644
--- a/fragment/fragment/api/restricted_current.txt
+++ b/fragment/fragment/api/restricted_current.txt
@@ -487,6 +487,7 @@
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public final class FragmentStrictMode {
     method public static androidx.fragment.app.strictmode.FragmentStrictMode.Policy getDefaultPolicy();
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public static void onSetUserVisibleHint(androidx.fragment.app.Fragment);
     method public static void setDefaultPolicy(androidx.fragment.app.strictmode.FragmentStrictMode.Policy);
   }
 
@@ -501,11 +502,16 @@
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public static final class FragmentStrictMode.Policy.Builder {
     ctor public FragmentStrictMode.Policy.Builder();
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy build();
+    method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder detectSetUserVisibleHint();
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyDeath();
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyListener(androidx.fragment.app.strictmode.FragmentStrictMode.OnViolationListener);
     method public androidx.fragment.app.strictmode.FragmentStrictMode.Policy.Builder penaltyLog();
   }
 
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public final class SetUserVisibleHintViolation extends androidx.fragment.app.strictmode.Violation {
+    ctor public SetUserVisibleHintViolation();
+  }
+
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public abstract class Violation extends java.lang.RuntimeException {
     ctor public Violation();
   }
diff --git a/fragment/fragment/build.gradle b/fragment/fragment/build.gradle
index 85ab2fd..05dc225 100644
--- a/fragment/fragment/build.gradle
+++ b/fragment/fragment/build.gradle
@@ -37,10 +37,10 @@
     api("androidx.collection:collection:1.1.0")
     api("androidx.viewpager:viewpager:1.0.0")
     api("androidx.loader:loader:1.0.0")
-    api("androidx.activity:activity:1.2.0")
-    api("androidx.lifecycle:lifecycle-livedata-core:2.3.0")
-    api("androidx.lifecycle:lifecycle-viewmodel:2.3.0")
-    api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.0")
+    api("androidx.activity:activity:1.2.2")
+    api("androidx.lifecycle:lifecycle-livedata-core:2.3.1")
+    api("androidx.lifecycle:lifecycle-viewmodel:2.3.1")
+    api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.1")
     api("androidx.savedstate:savedstate:1.1.0")
     api("androidx.annotation:annotation-experimental:1.0.0")
 
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/BackStackRecordTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/BackStackRecordTest.kt
index eeea0fa..ce059cb 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/BackStackRecordTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/BackStackRecordTest.kt
@@ -23,6 +23,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Assert.fail
 import org.junit.Rule
 import org.junit.Test
@@ -38,6 +39,218 @@
 
     @Test
     @UiThreadTest
+    fun testExpandCollapseOpsPrimaryNav() {
+        val initialFragment = StrictFragment()
+        val fm = activityRule.activity.supportFragmentManager
+        fm.beginTransaction()
+            .add(android.R.id.content, initialFragment)
+            .setPrimaryNavigationFragment(initialFragment)
+            .commitNow()
+
+        val replacementFragment = StrictFragment()
+        val backStackRecord = BackStackRecord(fm)
+        backStackRecord.setPrimaryNavigationFragment(replacementFragment)
+
+        backStackRecord.verifyOps {
+            verify(FragmentTransaction.OP_SET_PRIMARY_NAV, replacementFragment)
+        }
+
+        backStackRecord.expandOps(ArrayList(fm.fragments), initialFragment)
+
+        backStackRecord.verifyOps {
+            verify(FragmentTransaction.OP_UNSET_PRIMARY_NAV, initialFragment) {
+                fromExpandedOp = true
+            }
+            verify(FragmentTransaction.OP_SET_PRIMARY_NAV, replacementFragment) {
+                fromExpandedOp = true
+            }
+        }
+
+        backStackRecord.collapseOps()
+
+        backStackRecord.verifyOps {
+            verify(FragmentTransaction.OP_SET_PRIMARY_NAV, replacementFragment)
+        }
+    }
+
+    @Test
+    @UiThreadTest
+    fun testExpandCollapseOpsReplace() {
+        val initialFragment = StrictFragment()
+        val fm = activityRule.activity.supportFragmentManager
+        fm.beginTransaction().add(android.R.id.content, initialFragment).commitNow()
+
+        val replacementFragment = StrictFragment()
+        val backStackRecord = BackStackRecord(fm)
+        backStackRecord.replace(android.R.id.content, replacementFragment)
+
+        backStackRecord.verifyOps {
+            verify(FragmentTransaction.OP_REPLACE, replacementFragment)
+        }
+
+        backStackRecord.expandOps(ArrayList(fm.fragments), null)
+
+        backStackRecord.verifyOps {
+            verify(FragmentTransaction.OP_REMOVE, initialFragment) {
+                fromExpandedOp = true
+            }
+            verify(FragmentTransaction.OP_ADD, replacementFragment) {
+                fromExpandedOp = true
+            }
+        }
+
+        backStackRecord.collapseOps()
+
+        backStackRecord.verifyOps {
+            verify(FragmentTransaction.OP_REPLACE, replacementFragment)
+        }
+    }
+
+    @Test
+    @UiThreadTest
+    fun testExpandCollapseOpsReplacePrimaryNav() {
+        val initialFragment = StrictFragment()
+        val fm = activityRule.activity.supportFragmentManager
+        fm.beginTransaction()
+            .add(android.R.id.content, initialFragment)
+            .setPrimaryNavigationFragment(initialFragment)
+            .commitNow()
+
+        val replacementFragment = StrictFragment()
+        val backStackRecord = BackStackRecord(fm)
+        backStackRecord
+            .replace(android.R.id.content, replacementFragment)
+            .setPrimaryNavigationFragment(replacementFragment)
+
+        backStackRecord.verifyOps {
+            verify(FragmentTransaction.OP_REPLACE, replacementFragment)
+            verify(FragmentTransaction.OP_SET_PRIMARY_NAV, replacementFragment)
+        }
+
+        backStackRecord.expandOps(ArrayList(fm.fragments), initialFragment)
+
+        backStackRecord.verifyOps {
+            verify(FragmentTransaction.OP_UNSET_PRIMARY_NAV, initialFragment) {
+                fromExpandedOp = true
+            }
+            verify(FragmentTransaction.OP_REMOVE, initialFragment) {
+                fromExpandedOp = true
+            }
+            verify(FragmentTransaction.OP_ADD, replacementFragment) {
+                fromExpandedOp = true
+            }
+            verify(FragmentTransaction.OP_UNSET_PRIMARY_NAV) {
+                fromExpandedOp = true
+            }
+            verify(FragmentTransaction.OP_SET_PRIMARY_NAV, replacementFragment) {
+                fromExpandedOp = true
+            }
+        }
+
+        backStackRecord.collapseOps()
+
+        backStackRecord.verifyOps {
+            verify(FragmentTransaction.OP_REPLACE, replacementFragment)
+            verify(FragmentTransaction.OP_SET_PRIMARY_NAV, replacementFragment)
+        }
+    }
+
+    @Test
+    @UiThreadTest
+    fun testExpandCollapseOpsReplaceMultiple() {
+        val initialFragment = StrictFragment()
+        val addedFragment = StrictFragment()
+        val fm = activityRule.activity.supportFragmentManager
+        fm.beginTransaction()
+            .add(android.R.id.content, initialFragment)
+            .add(android.R.id.content, addedFragment)
+            .commitNow()
+
+        val replacementFragment = StrictFragment()
+        val backStackRecord = BackStackRecord(fm)
+        backStackRecord.replace(android.R.id.content, replacementFragment)
+
+        backStackRecord.verifyOps {
+            verify(FragmentTransaction.OP_REPLACE, replacementFragment)
+        }
+
+        backStackRecord.expandOps(ArrayList(fm.fragments), null)
+
+        backStackRecord.verifyOps {
+            verify(FragmentTransaction.OP_REMOVE, addedFragment) {
+                fromExpandedOp = true
+            }
+            verify(FragmentTransaction.OP_REMOVE, initialFragment) {
+                fromExpandedOp = true
+            }
+            verify(FragmentTransaction.OP_ADD, replacementFragment) {
+                fromExpandedOp = true
+            }
+        }
+
+        backStackRecord.collapseOps()
+
+        backStackRecord.verifyOps {
+            verify(FragmentTransaction.OP_REPLACE, replacementFragment)
+        }
+    }
+
+    @Test
+    @UiThreadTest
+    fun testExpandCollapseOpsReplaceMultiplePrimaryNav() {
+        val initialFragment = StrictFragment()
+        val addedFragment = StrictFragment()
+        val fm = activityRule.activity.supportFragmentManager
+        fm.beginTransaction()
+            .add(android.R.id.content, initialFragment)
+            .add(android.R.id.content, addedFragment)
+            .setPrimaryNavigationFragment(addedFragment)
+            .commitNow()
+
+        val replacementFragment = StrictFragment()
+        val backStackRecord = BackStackRecord(fm)
+        backStackRecord
+            .replace(android.R.id.content, replacementFragment)
+            .setPrimaryNavigationFragment(replacementFragment)
+
+        backStackRecord.verifyOps {
+            verify(FragmentTransaction.OP_REPLACE, replacementFragment)
+            verify(FragmentTransaction.OP_SET_PRIMARY_NAV, replacementFragment)
+        }
+
+        backStackRecord.expandOps(ArrayList(fm.fragments), addedFragment)
+
+        backStackRecord.verifyOps {
+            verify(FragmentTransaction.OP_UNSET_PRIMARY_NAV, addedFragment) {
+                fromExpandedOp = true
+            }
+            verify(FragmentTransaction.OP_REMOVE, addedFragment) {
+                fromExpandedOp = true
+            }
+            verify(FragmentTransaction.OP_REMOVE, initialFragment) {
+                fromExpandedOp = true
+            }
+            verify(FragmentTransaction.OP_ADD, replacementFragment) {
+                fromExpandedOp = true
+            }
+            verify(FragmentTransaction.OP_UNSET_PRIMARY_NAV) {
+                fromExpandedOp = true
+            }
+            verify(FragmentTransaction.OP_SET_PRIMARY_NAV, replacementFragment) {
+                fromExpandedOp = true
+            }
+        }
+
+        backStackRecord.collapseOps()
+
+        backStackRecord.verifyOps {
+            verify(FragmentTransaction.OP_REPLACE, replacementFragment)
+            verify(FragmentTransaction.OP_SET_PRIMARY_NAV, replacementFragment)
+        }
+    }
+
+    @Test
+    @UiThreadTest
     fun testHideOnFragmentWithAManager() {
         val viewModelStore1 = ViewModelStore()
         val fc1 = activityRule.startupFragmentController(viewModelStore1)
@@ -323,3 +536,51 @@
         }
     }
 }
+
+internal class BackStackRecordVerify(private val backStackRecord: BackStackRecord) {
+    var currentOp = 0
+
+    fun verify(
+        command: Int,
+        fragment: Fragment? = null,
+        block: BackStackRecordOpInfo.() -> Unit = {}
+    ) {
+        assertWithMessage(
+            "Cannot verify op $currentOp as there is only ${backStackRecord.mOps.size} operations"
+        ).that(backStackRecord.mOps.size).isAtLeast(currentOp + 1)
+        backStackRecord.mOps[currentOp].verify(currentOp, command, fragment, block)
+        currentOp++
+    }
+}
+
+internal fun BackStackRecord.verifyOps(block: BackStackRecordVerify.() -> Unit) {
+    val verify = BackStackRecordVerify(this).apply(block)
+    assertWithMessage("Not all operations were verified")
+        .that(verify.currentOp)
+        .isEqualTo(mOps.size)
+}
+
+internal data class BackStackRecordOpInfo(
+    var fromExpandedOp: Boolean = false
+)
+
+private fun FragmentTransaction.Op.verify(
+    opIndex: Int,
+    command: Int,
+    fragment: Fragment? = null,
+    block: BackStackRecordOpInfo.() -> Unit = {}
+) {
+    val (fromExpandedOp) = BackStackRecordOpInfo().apply { block() }
+
+    assertWithMessage("Operation $opIndex had the wrong command")
+        .that(mCmd)
+        .isEqualTo(command)
+    assertWithMessage("Operation $opIndex had the wrong fragment")
+        .that(mFragment)
+        .isSameInstanceAs(fragment)
+    assertWithMessage(
+        "Operation $opIndex " + (if (fromExpandedOp) "should" else "shouldn't") +
+            " be marked as from an expanded op"
+    ).that(mFromExpandedOp).isEqualTo(fromExpandedOp)
+    assertThat(mFromExpandedOp).isEqualTo(fromExpandedOp)
+}
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
index c5706ec..802a75e 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
@@ -799,6 +799,35 @@
         }
     }
 
+    @Test
+    fun removePopExitAnimation() {
+        waitForAnimationReady()
+        val fm = activityRule.activity.supportFragmentManager
+        val fragment1 = AnimationFragment()
+        val fragment2 = AnimationFragment()
+
+        fm.beginTransaction()
+            .setReorderingAllowed(true)
+            .setCustomAnimations(ENTER, EXIT, POP_ENTER, POP_EXIT)
+            .add(R.id.fragmentContainer, fragment1, "fragment1")
+            .addToBackStack("fragment1")
+            .commit()
+        activityRule.waitForExecution()
+
+        activityRule.runOnUiThread {
+            fm.popBackStack()
+            fm.beginTransaction()
+                .setReorderingAllowed(true)
+                .setCustomAnimations(ENTER, EXIT, POP_ENTER, POP_EXIT)
+                .replace(R.id.fragmentContainer, fragment2, "fragment2")
+                .addToBackStack("fragment2")
+                .commit()
+        }
+        activityRule.waitForExecution()
+
+        assertThat(fragment1.loadedAnimation).isEqualTo(EXIT)
+    }
+
     private fun assertEnterPopExit(fragment: AnimationFragment) {
         assertFragmentAnimation(fragment, 1, true, ENTER)
 
@@ -848,7 +877,10 @@
         assertThat(fragment.animation).isNotNull()
         assertThat(fragment.animation!!.waitForEnd(1000)).isTrue()
         assertThat(fragment.animation?.hasStarted()!!).isTrue()
-        assertThat(fragment.nextAnim).isEqualTo(0)
+        assertThat(fragment.enterAnim).isEqualTo(0)
+        assertThat(fragment.exitAnim).isEqualTo(0)
+        assertThat(fragment.popEnterAnim).isEqualTo(0)
+        assertThat(fragment.popExitAnim).isEqualTo(0)
     }
 
     private fun assertPostponed(fragment: AnimationFragment, expectedAnimators: Int) {
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/strictmode/FragmentStrictModeTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/strictmode/FragmentStrictModeTest.kt
index 5df979f..0e0b17b 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/strictmode/FragmentStrictModeTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/strictmode/FragmentStrictModeTest.kt
@@ -127,4 +127,18 @@
             assertThat(thread).isEqualTo(Looper.getMainLooper().thread)
         }
     }
+
+    @Test
+    public fun detectSetUserVisibleHint() {
+        var violation: Violation? = null
+        val policy = FragmentStrictMode.Policy.Builder()
+            .detectSetUserVisibleHint()
+            .penaltyListener { violation = it }
+            .build()
+        FragmentStrictMode.setDefaultPolicy(policy)
+
+        @Suppress("DEPRECATION")
+        StrictFragment().userVisibleHint = true
+        assertThat(violation).isInstanceOf(SetUserVisibleHintViolation::class.java)
+    }
 }
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
index 5f968ae..448ee87 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecord.java
@@ -404,34 +404,30 @@
             final Op op = mOps.get(opNum);
             final Fragment f = op.mFragment;
             if (f != null) {
+                f.setPopDirection(false);
+                f.setAnimations(op.mEnterAnim, op.mExitAnim, op.mPopEnterAnim, op.mPopExitAnim);
                 f.setNextTransition(mTransition);
                 f.setSharedElementNames(mSharedElementSourceNames, mSharedElementTargetNames);
             }
             switch (op.mCmd) {
                 case OP_ADD:
-                    f.setNextAnim(op.mEnterAnim);
                     mManager.setExitAnimationOrder(f, false);
                     mManager.addFragment(f);
                     break;
                 case OP_REMOVE:
-                    f.setNextAnim(op.mExitAnim);
                     mManager.removeFragment(f);
                     break;
                 case OP_HIDE:
-                    f.setNextAnim(op.mExitAnim);
                     mManager.hideFragment(f);
                     break;
                 case OP_SHOW:
-                    f.setNextAnim(op.mEnterAnim);
                     mManager.setExitAnimationOrder(f, false);
                     mManager.showFragment(f);
                     break;
                 case OP_DETACH:
-                    f.setNextAnim(op.mExitAnim);
                     mManager.detachFragment(f);
                     break;
                 case OP_ATTACH:
-                    f.setNextAnim(op.mEnterAnim);
                     mManager.setExitAnimationOrder(f, false);
                     mManager.attachFragment(f);
                     break;
@@ -471,35 +467,31 @@
             final Op op = mOps.get(opNum);
             Fragment f = op.mFragment;
             if (f != null) {
+                f.setPopDirection(true);
+                f.setAnimations(op.mEnterAnim, op.mExitAnim, op.mPopEnterAnim, op.mPopExitAnim);
                 f.setNextTransition(FragmentManager.reverseTransit(mTransition));
                 // Reverse the target and source names for pop operations
                 f.setSharedElementNames(mSharedElementTargetNames, mSharedElementSourceNames);
             }
             switch (op.mCmd) {
                 case OP_ADD:
-                    f.setNextAnim(op.mPopExitAnim);
                     mManager.setExitAnimationOrder(f, true);
                     mManager.removeFragment(f);
                     break;
                 case OP_REMOVE:
-                    f.setNextAnim(op.mPopEnterAnim);
                     mManager.addFragment(f);
                     break;
                 case OP_HIDE:
-                    f.setNextAnim(op.mPopEnterAnim);
                     mManager.showFragment(f);
                     break;
                 case OP_SHOW:
-                    f.setNextAnim(op.mPopExitAnim);
                     mManager.setExitAnimationOrder(f, true);
                     mManager.hideFragment(f);
                     break;
                 case OP_DETACH:
-                    f.setNextAnim(op.mPopEnterAnim);
                     mManager.attachFragment(f);
                     break;
                 case OP_ATTACH:
-                    f.setNextAnim(op.mPopExitAnim);
                     mManager.setExitAnimationOrder(f, true);
                     mManager.detachFragment(f);
                     break;
@@ -600,6 +592,7 @@
                         opNum--;
                     } else {
                         op.mCmd = OP_ADD;
+                        op.mFromExpandedOp = true;
                         added.add(f);
                     }
                 }
@@ -607,7 +600,8 @@
                 case OP_SET_PRIMARY_NAV: {
                     // It's ok if this is null, that means we will restore to no active
                     // primary navigation fragment on a pop.
-                    mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, oldPrimaryNav));
+                    mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, oldPrimaryNav, true));
+                    op.mFromExpandedOp = true;
                     opNum++;
                     // Will be set by the OP_SET_PRIMARY_NAV we inserted before when run
                     oldPrimaryNav = op.mFragment;
@@ -654,6 +648,44 @@
         return oldPrimaryNav;
     }
 
+    /**
+     * Removes any Ops expanded by {@link #expandOps(ArrayList, Fragment)},
+     * reverting them back to their collapsed form.
+     */
+    void collapseOps() {
+        for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) {
+            final Op op = mOps.get(opNum);
+            if (!op.mFromExpandedOp) {
+                continue;
+            }
+            if (op.mCmd == OP_SET_PRIMARY_NAV) {
+                // OP_SET_PRIMARY_NAV is always expanded to two ops:
+                // 1. The OP_SET_PRIMARY_NAV we want to keep
+                op.mFromExpandedOp = false;
+                // And the OP_UNSET_PRIMARY_NAV we want to remove
+                mOps.remove(opNum - 1);
+                opNum--;
+            } else {
+                // Handle the collapse of an OP_REPLACE, which could start
+                // with either an OP_ADD (the usual case) or an OP_REMOVE
+                // (if the dev explicitly called add() earlier in the transaction)
+                int containerId = op.mFragment.mContainerId;
+                // Swap this expanded op with a replace
+                op.mCmd = OP_REPLACE;
+                op.mFromExpandedOp = false;
+                // And remove all other expanded ops with the same containerId
+                for (int replaceOpNum = opNum - 1; replaceOpNum >= 0; replaceOpNum--) {
+                    final Op potentialReplaceOp = mOps.get(replaceOpNum);
+                    if (potentialReplaceOp.mFromExpandedOp
+                            && potentialReplaceOp.mFragment.mContainerId == containerId) {
+                        mOps.remove(replaceOpNum);
+                        opNum--;
+                    }
+                }
+            }
+        }
+    }
+
     boolean isPostponed() {
         for (int opNum = 0; opNum < mOps.size(); opNum++) {
             final Op op = mOps.get(opNum);
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecordState.java b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecordState.java
index 94ac214..03f8e38 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecordState.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/BackStackRecordState.java
@@ -63,7 +63,7 @@
             final BackStackRecord.Op op = bse.mOps.get(opNum);
             mOps[pos++] = op.mCmd;
             mFragmentWhos.add(op.mFragment != null ? op.mFragment.mWho : null);
-            mOps[pos++] = op.mTopmostFragment ? 1 : 0;
+            mOps[pos++] = op.mFromExpandedOp ? 1 : 0;
             mOps[pos++] = op.mEnterAnim;
             mOps[pos++] = op.mExitAnim;
             mOps[pos++] = op.mPopEnterAnim;
@@ -154,7 +154,7 @@
             }
             op.mOldMaxState = Lifecycle.State.values()[mOldMaxLifecycleStates[num]];
             op.mCurrentMaxState = Lifecycle.State.values()[mCurrentMaxLifecycleStates[num]];
-            op.mTopmostFragment = mOps[pos++] != 0;
+            op.mFromExpandedOp = mOps[pos++] != 0;
             op.mEnterAnim = mOps[pos++];
             op.mExitAnim = mOps[pos++];
             op.mPopEnterAnim = mOps[pos++];
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.java b/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.java
index 247065a..88f0e80 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/DefaultSpecialEffectsController.java
@@ -89,7 +89,7 @@
             CancellationSignal animCancellationSignal = new CancellationSignal();
             operation.markStartedSpecialEffect(animCancellationSignal);
             // Add the animation special effect
-            animations.add(new AnimationInfo(operation, animCancellationSignal));
+            animations.add(new AnimationInfo(operation, animCancellationSignal, isPop));
 
             // Create the transition CancellationSignal
             CancellationSignal transitionCancellationSignal = new CancellationSignal();
@@ -777,12 +777,15 @@
 
     private static class AnimationInfo extends SpecialEffectsInfo {
 
+        private boolean mIsPop;
         private boolean mLoadedAnim = false;
         @Nullable
         private FragmentAnim.AnimationOrAnimator mAnimation;
 
-        AnimationInfo(@NonNull Operation operation, @NonNull CancellationSignal signal) {
+        AnimationInfo(@NonNull Operation operation, @NonNull CancellationSignal signal,
+                boolean isPop) {
             super(operation, signal);
+            mIsPop = isPop;
         }
 
         @Nullable
@@ -792,7 +795,8 @@
             }
             mAnimation = FragmentAnim.loadAnimation(context,
                     getOperation().getFragment(),
-                    getOperation().getFinalState() == Operation.State.VISIBLE);
+                    getOperation().getFinalState() == Operation.State.VISIBLE,
+                    mIsPop);
             mLoadedAnim = true;
             return mAnimation;
         }
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
index 1f8e710..593dc2a 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
@@ -72,6 +72,7 @@
 import androidx.core.app.ActivityOptionsCompat;
 import androidx.core.app.SharedElementCallback;
 import androidx.core.view.LayoutInflaterCompat;
+import androidx.fragment.app.strictmode.FragmentStrictMode;
 import androidx.lifecycle.HasDefaultViewModelProviderFactory;
 import androidx.lifecycle.Lifecycle;
 import androidx.lifecycle.LifecycleEventObserver;
@@ -1319,6 +1320,7 @@
      */
     @Deprecated
     public void setUserVisibleHint(boolean isVisibleToUser) {
+        FragmentStrictMode.onSetUserVisibleHint(this);
         if (!mUserVisibleHint && isVisibleToUser && mState < STARTED
                 && mFragmentManager != null && isAdded() && mIsCreated) {
             mFragmentManager.performPendingDeferredStart(
@@ -2850,8 +2852,19 @@
                     writer.print(" mTargetRequestCode=");
                     writer.println(mTargetRequestCode);
         }
-        if (getNextAnim() != 0) {
-            writer.print(prefix); writer.print("mNextAnim="); writer.println(getNextAnim());
+        writer.print(prefix); writer.print("mPopDirection="); writer.println(getPopDirection());
+        if (getEnterAnim() != 0) {
+            writer.print(prefix); writer.print("getEnterAnim="); writer.println(getEnterAnim());
+        }
+        if (getExitAnim() != 0) {
+            writer.print(prefix); writer.print("getExitAnim="); writer.println(getExitAnim());
+        }
+        if (getPopEnterAnim() != 0) {
+            writer.print(prefix); writer.print("getPopEnterAnim=");
+            writer.println(getPopEnterAnim());
+        }
+        if (getPopExitAnim() != 0) {
+            writer.print(prefix); writer.print("getPopExitAnim="); writer.println(getPopExitAnim());
         }
         if (mContainer != null) {
             writer.print(prefix); writer.print("mContainer="); writer.println(mContainer);
@@ -3255,18 +3268,56 @@
         return mAnimationInfo;
     }
 
-    int getNextAnim() {
+    void setAnimations(int enter, int exit, int popEnter, int popExit) {
+        if (mAnimationInfo == null && enter == 0 && exit == 0 && popEnter == 0 && popExit == 0) {
+            return; // no change!
+        }
+        ensureAnimationInfo().mEnterAnim = enter;
+        ensureAnimationInfo().mExitAnim = exit;
+        ensureAnimationInfo().mPopEnterAnim = popEnter;
+        ensureAnimationInfo().mPopExitAnim = popExit;
+    }
+
+    int getEnterAnim() {
         if (mAnimationInfo == null) {
             return 0;
         }
-        return mAnimationInfo.mNextAnim;
+        return mAnimationInfo.mEnterAnim;
     }
 
-    void setNextAnim(int animResourceId) {
-        if (mAnimationInfo == null && animResourceId == 0) {
+    int getExitAnim() {
+        if (mAnimationInfo == null) {
+            return 0;
+        }
+        return mAnimationInfo.mExitAnim;
+    }
+
+    int getPopEnterAnim() {
+        if (mAnimationInfo == null) {
+            return 0;
+        }
+        return mAnimationInfo.mPopEnterAnim;
+    }
+
+    int getPopExitAnim() {
+        if (mAnimationInfo == null) {
+            return 0;
+        }
+        return mAnimationInfo.mPopExitAnim;
+    }
+
+    boolean getPopDirection() {
+        if (mAnimationInfo == null) {
+            return false;
+        }
+        return mAnimationInfo.mIsPop;
+    }
+
+    void setPopDirection(boolean isPop) {
+        if (mAnimationInfo == null) {
             return; // no change!
         }
-        ensureAnimationInfo().mNextAnim = animResourceId;
+        ensureAnimationInfo().mIsPop = isPop;
     }
 
     int getNextTransition() {
@@ -3516,8 +3567,14 @@
         // animator instead of an animation.
         Animator mAnimator;
 
-        // If app has requested a specific animation, this is the one to use.
-        int mNextAnim;
+        // If app requests the animation direction, this is what to use
+        boolean mIsPop;
+
+        // All possible animations
+        int mEnterAnim;
+        int mExitAnim;
+        int mPopEnterAnim;
+        int mPopExitAnim;
 
         // If app has requested a specific transition, this is the one to use.
         int mNextTransition;
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentAnim.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentAnim.java
index df536f1..28116cf 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentAnim.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentAnim.java
@@ -43,11 +43,11 @@
     }
 
     static AnimationOrAnimator loadAnimation(@NonNull Context context,
-            @NonNull Fragment fragment, boolean enter) {
+            @NonNull Fragment fragment, boolean enter, boolean isPop) {
         int transit = fragment.getNextTransition();
-        int nextAnim = fragment.getNextAnim();
-        // Clear the Fragment animation
-        fragment.setNextAnim(0);
+        int nextAnim = getNextAnim(fragment, enter, isPop);
+        // Clear the Fragment animations
+        fragment.setAnimations(0, 0, 0, 0);
         // We do not need to keep up with the removing Fragment after we get its next animation.
         // If transactions do not allow reordering, this will always be true and the visible
         // removing fragment will be cleared. If reordering is allowed, this will only be true
@@ -118,6 +118,22 @@
         return null;
     }
 
+    private static int getNextAnim(Fragment fragment, boolean enter, boolean isPop) {
+        if (isPop) {
+            if (enter) {
+                return fragment.getPopEnterAnim();
+            } else {
+                return fragment.getPopExitAnim();
+            }
+        } else {
+            if (enter) {
+                return fragment.getEnterAnim();
+            } else {
+                return fragment.getExitAnim();
+            }
+        }
+    }
+
     /**
      * Animates the removal of a fragment with the given animator or animation. After animating,
      * the fragment's view will be removed from the hierarchy.
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
index 6abf46e..6484fae 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -1442,7 +1442,7 @@
                                         && f.mView.getVisibility() == View.VISIBLE
                                         && f.mPostponedAlpha >= 0) {
                                     anim = FragmentAnim.loadAnimation(mHost.getContext(),
-                                            f, false);
+                                            f, false, f.getPopDirection());
                                 }
                                 f.mPostponedAlpha = 0;
                                 // Robolectric tests do not post the animation like a real device
@@ -1558,7 +1558,7 @@
     private void completeShowHideFragment(@NonNull final Fragment fragment) {
         if (fragment.mView != null) {
             FragmentAnim.AnimationOrAnimator anim = FragmentAnim.loadAnimation(
-                    mHost.getContext(), fragment, !fragment.mHidden);
+                    mHost.getContext(), fragment, !fragment.mHidden, fragment.getPopDirection());
             if (anim != null && anim.animator != null) {
                 anim.animator.setTarget(fragment.mView);
                 if (fragment.mHidden) {
@@ -1630,7 +1630,7 @@
                 f.mIsNewlyAdded = false;
                 // run animations:
                 FragmentAnim.AnimationOrAnimator anim = FragmentAnim.loadAnimation(
-                        mHost.getContext(), f, true);
+                        mHost.getContext(), f, true, f.getPopDirection());
                 if (anim != null) {
                     if (anim.animation != null) {
                         f.mView.startAnimation(anim.animation);
@@ -2437,12 +2437,14 @@
      */
     private void setVisibleRemovingFragment(@NonNull Fragment f) {
         ViewGroup container = getFragmentContainer(f);
-        if (container != null && f.getNextAnim() > 0) {
+        if (container != null
+                && f.getEnterAnim() + f.getExitAnim() + f.getPopEnterAnim() + f.getPopExitAnim() > 0
+        ) {
             if (container.getTag(R.id.visible_removing_fragment_view_tag) == null) {
                 container.setTag(R.id.visible_removing_fragment_view_tag, f);
             }
             ((Fragment) container.getTag(R.id.visible_removing_fragment_view_tag))
-                    .setNextAnim(f.getNextAnim());
+                    .setPopDirection(f.getPopDirection());
         }
     }
 
@@ -2628,12 +2630,16 @@
             HashSet<Fragment> addedFragments = new HashSet<>();
             for (FragmentTransaction.Op op : record.mOps) {
                 Fragment f = op.mFragment;
-                if (f != null && !op.mTopmostFragment && !allFragments.contains(f)) {
+                if (f == null) {
+                    continue;
+                }
+                if (!op.mFromExpandedOp || op.mCmd == FragmentTransaction.OP_ADD
+                        || op.mCmd == FragmentTransaction.OP_SET_PRIMARY_NAV) {
                     allFragments.add(f);
                     affectedFragments.add(f);
-                    if (op.mCmd == FragmentTransaction.OP_ADD) {
-                        addedFragments.add(f);
-                    }
+                }
+                if (op.mCmd == FragmentTransaction.OP_ADD) {
+                    addedFragments.add(f);
                 }
             }
             affectedFragments.removeAll(addedFragments);
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransaction.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransaction.java
index b94e94c..d6a2636 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransaction.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentTransaction.java
@@ -63,7 +63,7 @@
     static final class Op {
         int mCmd;
         Fragment mFragment;
-        boolean mTopmostFragment;
+        boolean mFromExpandedOp;
         int mEnterAnim;
         int mExitAnim;
         int mPopEnterAnim;
@@ -77,15 +77,15 @@
         Op(int cmd, Fragment fragment) {
             this.mCmd = cmd;
             this.mFragment = fragment;
-            this.mTopmostFragment = false;
+            this.mFromExpandedOp = false;
             this.mOldMaxState = Lifecycle.State.RESUMED;
             this.mCurrentMaxState = Lifecycle.State.RESUMED;
         }
 
-        Op(int cmd, Fragment fragment, boolean topmostFragment) {
+        Op(int cmd, Fragment fragment, boolean fromExpandedOp) {
             this.mCmd = cmd;
             this.mFragment = fragment;
-            this.mTopmostFragment = topmostFragment;
+            this.mFromExpandedOp = fromExpandedOp;
             this.mOldMaxState = Lifecycle.State.RESUMED;
             this.mCurrentMaxState = Lifecycle.State.RESUMED;
         }
@@ -93,7 +93,7 @@
         Op(int cmd, @NonNull Fragment fragment, Lifecycle.State state) {
             this.mCmd = cmd;
             this.mFragment = fragment;
-            this.mTopmostFragment = false;
+            this.mFromExpandedOp = false;
             this.mOldMaxState = fragment.mMaxState;
             this.mCurrentMaxState = state;
         }
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/FragmentStrictMode.java b/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/FragmentStrictMode.java
index 21a6b9d..659a6af 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/FragmentStrictMode.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/FragmentStrictMode.java
@@ -49,7 +49,9 @@
 
     private enum Flag {
         PENALTY_LOG,
-        PENALTY_DEATH
+        PENALTY_DEATH,
+
+        DETECT_SET_USER_VISIBLE_HINT
     }
 
     private FragmentStrictMode() {}
@@ -140,6 +142,14 @@
                 return this;
             }
 
+            /** Detects calls to #{@link Fragment#setUserVisibleHint}. */
+            @NonNull
+            @SuppressLint("BuilderSetStyle")
+            public Builder detectSetUserVisibleHint() {
+                flags.add(Flag.DETECT_SET_USER_VISIBLE_HINT);
+                return this;
+            }
+
             /**
              * Construct the Policy instance.
              *
@@ -185,9 +195,25 @@
         return defaultPolicy;
     }
 
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    public static void onSetUserVisibleHint(@NonNull Fragment fragment) {
+        Policy policy = getNearestPolicy(fragment);
+        if (policy.flags.contains(Flag.DETECT_SET_USER_VISIBLE_HINT)) {
+            handlePolicyViolation(fragment, policy, new SetUserVisibleHintViolation());
+        }
+    }
+
     @VisibleForTesting
-    static void onPolicyViolation(@NonNull Fragment fragment, @NonNull final Violation violation) {
-        final Policy policy = getNearestPolicy(fragment);
+    static void onPolicyViolation(@NonNull Fragment fragment, @NonNull Violation violation) {
+        Policy policy = getNearestPolicy(fragment);
+        handlePolicyViolation(fragment, policy, violation);
+    }
+
+    private static void handlePolicyViolation(
+            @NonNull Fragment fragment,
+            @NonNull final Policy policy,
+            @NonNull final Violation violation
+    ) {
         final String fragmentName = fragment.getClass().getName();
 
         if (policy.flags.contains(Flag.PENALTY_LOG)) {
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/SetUserVisibleHintViolation.java b/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/SetUserVisibleHintViolation.java
new file mode 100644
index 0000000..d356887
--- /dev/null
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/strictmode/SetUserVisibleHintViolation.java
@@ -0,0 +1,24 @@
+/*
+ * 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.fragment.app.strictmode;
+
+import androidx.annotation.RestrictTo;
+
+/** See #{@link FragmentStrictMode.Policy.Builder#detectSetUserVisibleHint()}. */
+@RestrictTo(RestrictTo.Scope.LIBRARY) // TODO: Make API public as soon as we have a few checks
+public final class SetUserVisibleHintViolation extends Violation {
+}
diff --git a/gradlew b/gradlew
index f5d3479c..4feb997 100755
--- a/gradlew
+++ b/gradlew
@@ -289,7 +289,9 @@
   else
     wrapper=""
   fi
-  if $wrapper "$JAVACMD" "${JVM_OPTS[@]}" $TMPDIR_ARG -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain $HOME_SYSTEM_PROPERTY_ARGUMENT $TMPDIR_ARG "$ORG_GRADLE_JVMARGS" "$@"; then
+
+  PROJECT_CACHE_DIR_ARGUMENT="--project-cache-dir $OUT_DIR/gradle-project-cache"
+  if $wrapper "$JAVACMD" "${JVM_OPTS[@]}" $TMPDIR_ARG -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain $HOME_SYSTEM_PROPERTY_ARGUMENT $TMPDIR_ARG $PROJECT_CACHE_DIR_ARGUMENT "$ORG_GRADLE_JVMARGS" "$@"; then
     return 0
   else
     tryToDiagnosePossibleDaemonFailure
diff --git a/hilt/hilt-navigation/lint-baseline.xml b/hilt/hilt-navigation/lint-baseline.xml
deleted file mode 100644
index 8f1aa4b..0000000
--- a/hilt/hilt-navigation/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-</issues>
diff --git a/hilt/integration-tests/workerapp/src/main/AndroidManifest.xml b/hilt/integration-tests/workerapp/src/main/AndroidManifest.xml
index cf3d8fc..50f1a690 100644
--- a/hilt/integration-tests/workerapp/src/main/AndroidManifest.xml
+++ b/hilt/integration-tests/workerapp/src/main/AndroidManifest.xml
@@ -28,9 +28,17 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+
         <provider
-            android:name="androidx.work.impl.WorkManagerInitializer"
-            android:authorities="${applicationId}.workmanager-init"
-            tools:node="remove" />
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            android:exported="false"
+            tools:node="merge">
+            <!-- Remove initializer for WorkManager on-demand initialization -->
+            <meta-data
+                android:name="androidx.work.impl.WorkManagerInitializer"
+                android:value="@string/androidx_startup"
+                tools:node="remove" />
+        </provider>
     </application>
 </manifest>
diff --git a/inspection/inspection-testing/lint-baseline.xml b/inspection/inspection-testing/lint-baseline.xml
deleted file mode 100644
index 8f1aa4b..0000000
--- a/inspection/inspection-testing/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-</issues>
diff --git a/inspection/inspection/lint-baseline.xml b/inspection/inspection/lint-baseline.xml
index 05edf40..367e8df 100644
--- a/inspection/inspection/lint-baseline.xml
+++ b/inspection/inspection/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
+<issues format="5" by="lint 4.2.0-beta04" client="gradle" variant="debug" version="4.2.0-beta04">
 
     <issue
         id="UnknownNullness"
diff --git a/jetifier/jetifier/migration.config b/jetifier/jetifier/migration.config
index bd39e34..de39296 100644
--- a/jetifier/jetifier/migration.config
+++ b/jetifier/jetifier/migration.config
@@ -485,11 +485,7 @@
       "to": "android/support/v4/app/SpecialEffectsController{0}"
     },
     {
-      "from": "androidx/fragment/app/strictmode/FragmentStrictMode(.*)",
-      "to": "ignore"
-    },
-    {
-      "from": "androidx/fragment/app/strictmode/Violation(.*)",
+      "from": "androidx/fragment/app/strictmode/(.*)",
       "to": "ignore"
     },
     {
diff --git a/jetifier/jetifier/standalone/src/main/kotlin/com/android/tools/build/jetifier/standalone/TopOfTreeBuilder.kt b/jetifier/jetifier/standalone/src/main/kotlin/com/android/tools/build/jetifier/standalone/TopOfTreeBuilder.kt
index d843e8b..b431873 100644
--- a/jetifier/jetifier/standalone/src/main/kotlin/com/android/tools/build/jetifier/standalone/TopOfTreeBuilder.kt
+++ b/jetifier/jetifier/standalone/src/main/kotlin/com/android/tools/build/jetifier/standalone/TopOfTreeBuilder.kt
@@ -57,14 +57,16 @@
                 val name = pomFile.relativePath.toFile().nameWithoutExtension
                 val nameAar = name + ".aar"
                 val nameJar = name + ".jar"
-                val artifactFile = libFiles.first {
+                val artifactFile = libFiles.firstOrNull {
                     it.fileName == nameAar || it.fileName == nameJar
                 }
                 val nameSources = name + "-sources.jar"
-                val sourcesFile = libFiles.first {
+                val sourcesFile = libFiles.firstOrNull {
                     it.fileName == nameSources
                 }
-                process(pomFile, artifactFile, sourcesFile, newFiles)
+                if (artifactFile != null && sourcesFile != null) {
+                    process(pomFile, artifactFile, sourcesFile, newFiles)
+                }
             }
         }
 
diff --git a/lifecycle/OWNERS b/lifecycle/OWNERS
index fc51372..77b5892 100644
--- a/lifecycle/OWNERS
+++ b/lifecycle/OWNERS
@@ -1,2 +1,5 @@
 [email protected]
[email protected]
\ No newline at end of file
[email protected]
+
+per-file settings.gradle = [email protected], [email protected]
+
diff --git a/lifecycle/lifecycle-livedata-core-ktx-lint/src/main/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetector.kt b/lifecycle/lifecycle-livedata-core-ktx-lint/src/main/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetector.kt
index 7c41003..31c31a6 100644
--- a/lifecycle/lifecycle-livedata-core-ktx-lint/src/main/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetector.kt
+++ b/lifecycle/lifecycle-livedata-core-ktx-lint/src/main/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetector.kt
@@ -16,20 +16,19 @@
 
 package androidx.lifecycle.lint
 
-import com.android.tools.lint.checks.DataFlowAnalyzer
+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.Detector.UastScanner
 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.LintFix
 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 com.android.tools.lint.detector.api.UastLintUtils
 import com.android.tools.lint.detector.api.isKotlin
 import com.intellij.psi.PsiClassType
-import com.intellij.psi.PsiMethod
 import com.intellij.psi.PsiVariable
 import org.jetbrains.kotlin.psi.KtCallExpression
 import org.jetbrains.kotlin.psi.KtNullableType
@@ -39,7 +38,6 @@
 import org.jetbrains.uast.UClass
 import org.jetbrains.uast.UElement
 import org.jetbrains.uast.UReferenceExpression
-import org.jetbrains.uast.getParentOfType
 import org.jetbrains.uast.getUastParentOfType
 import org.jetbrains.uast.isNullLiteral
 import org.jetbrains.uast.kotlin.KotlinUField
@@ -50,7 +48,7 @@
  * Lint check for ensuring that [androidx.lifecycle.MutableLiveData] values are never null when
  * the type is defined as non-nullable in Kotlin.
  */
-class NonNullableMutableLiveDataDetector : Detector(), SourceCodeScanner {
+class NonNullableMutableLiveDataDetector : Detector(), UastScanner {
 
     companion object {
         val ISSUE = Issue.Companion.create(
@@ -74,80 +72,73 @@
         )
     }
 
-    override fun getApplicableMethodNames(): List<String>? = listOf("setValue", "postValue")
+    val typesMap = HashMap<String, KtTypeReference>()
 
-    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
-        if (!isKotlin(node.sourcePsi) || !context.evaluator.isMemberInSubClassOf(
-                method,
-                "androidx.lifecycle.LiveData", false
-            )
-        ) return
+    val methods = listOf("setValue", "postValue")
 
-        val fieldTypes = mutableListOf<KtTypeReference>()
+    override fun getApplicableUastTypes(): List<Class<out UElement>>? {
+        return listOf(UCallExpression::class.java, UClass::class.java)
+    }
 
-        val analyzer = node.getParentOfType<UClass>(UClass::class.java, true) ?: return
-        analyzer.accept(object : DataFlowAnalyzer(listOf(node)) {
-            override fun visitClass(node: UClass): Boolean {
+    override fun createUastHandler(context: JavaContext): UElementHandler? {
+        return object : UElementHandler() {
+            override fun visitClass(node: UClass) {
                 for (element in node.uastDeclarations) {
                     if (element is KotlinUField) {
-                        (element.sourcePsi?.children?.get(0) as? KtCallExpression)
-                            ?.typeArguments?.singleOrNull()?.typeReference?.let {
-                                fieldTypes.add(it)
-                            }
+                        getFieldTypeReference(element)?.let {
+                            // map the variable name to the type reference of its expression.
+                            typesMap.put(element.name, it)
+                        }
                     }
                 }
-                return super.visitClass(node)
             }
-        })
 
-        val receiverType = node.receiverType as PsiClassType
-        val liveDataType = if (fieldTypes.isNullOrEmpty()) {
-            if (receiverType.hasParameters()) {
-                val receiver =
-                    (node.receiver as? KotlinUSimpleReferenceExpression)?.resolve() ?: return
-                val variable = (receiver as? PsiVariable) ?: return
-                val assignment = UastLintUtils.findLastAssignment(variable, node) ?: return
-                val constructorExpression = assignment.sourcePsi as? KtCallExpression
-                constructorExpression?.typeArguments?.singleOrNull()?.typeReference
-            } else {
-                getTypeArg(receiverType)
-            } ?: return
-        } else {
-            fieldTypes[0]
-        }
-
-        if (liveDataType.typeElement !is KtNullableType) {
-            val fixes = mutableListOf<LintFix>()
-            if (context.getLocation(liveDataType).file == context.file) {
-                // Quick Fixes can only be applied to current file
-                fixes.add(
-                    fix()
-                        .name("Change `LiveData` type to nullable")
-                        .replace()
-                        .with("?")
-                        .range(context.getLocation(liveDataType))
-                        .end()
-                        .build()
-                )
+            private fun getFieldTypeReference(element: KotlinUField): KtTypeReference? {
+                // We need to extract type from the expression
+                // Given the field `val liveDataField = MutableLiveData<Boolean>()`
+                // expression: `MutableLiveData<Boolean>()`
+                // argument: `Boolean`
+                val expression = element.sourcePsi?.children?.get(0) as? KtCallExpression
+                val argument = expression?.typeArguments?.singleOrNull()
+                return argument?.typeReference
             }
-            val argument = node.valueArguments[0]
-            if (argument.isNullLiteral()) {
-                // Don't report null!! quick fix.
-                report(
-                    context, argument, "Cannot set non-nullable LiveData value to `null`",
-                    fixes
-                )
-            } else if (argument.isNullable()) {
-                fixes.add(
-                    fix()
-                        .name("Add non-null asserted (!!) call")
-                        .replace()
-                        .with("!!")
-                        .range(context.getLocation(argument))
-                        .end()
-                        .build()
-                )
-                report(context, argument, "Expected non-nullable value", fixes)
+
+            override fun visitCallExpression(node: UCallExpression) {
+                if (!isKotlin(node.sourcePsi) || !methods.contains(node.methodName) ||
+                    !context.evaluator.isMemberInSubClassOf(
+                            node.resolve()!!, "androidx.lifecycle.LiveData", false
+                        )
+                ) return
+
+                val receiverType = node.receiverType as? PsiClassType
+                var liveDataType =
+                    if (receiverType != null && receiverType.hasParameters()) {
+                        val receiver =
+                            (node.receiver as? KotlinUSimpleReferenceExpression)?.resolve()
+                        val variable = (receiver as? PsiVariable)
+                        val assignment = variable?.let {
+                            UastLintUtils.findLastAssignment(it, node)
+                        }
+                        val constructorExpression = assignment?.sourcePsi as? KtCallExpression
+                        constructorExpression?.typeArguments?.singleOrNull()?.typeReference
+                    } else {
+                        getTypeArg(receiverType)
+                    }
+                if (liveDataType == null) {
+                    liveDataType = typesMap[getVariableName(node)] ?: return
+                }
+                checkNullability(liveDataType, context, node)
+            }
+
+            private fun getVariableName(node: UCallExpression): String? {
+                // We need to get the variable this expression is being assigned to
+                // Given the assignment `liveDataField.value = null`
+                // node.sourcePsi : `value`
+                // dot: `.`
+                // variable: `liveDataField`
+                val dot = node.sourcePsi?.prevSibling
+                val variable = dot?.prevSibling?.firstChild
+                return variable?.text
             }
         }
     }
@@ -158,7 +149,10 @@
      * @param classType The [PsiClassType] to search
      * @return The LiveData type argument.
      */
-    private fun getTypeArg(classType: PsiClassType): KtTypeReference {
+    fun getTypeArg(classType: PsiClassType?): KtTypeReference? {
+        if (classType == null) {
+            return null
+        }
         val cls = classType.resolve().getUastParentOfType<UClass>()
         val parentPsiType = cls?.superClassType as PsiClassType
         if (parentPsiType.hasParameters()) {
@@ -169,6 +163,39 @@
         return getTypeArg(parentPsiType)
     }
 
+    fun checkNullability(
+        liveDataType: KtTypeReference,
+        context: JavaContext,
+        node: UCallExpression
+    ) {
+        if (liveDataType.typeElement !is KtNullableType) {
+            val fixes = mutableListOf<LintFix>()
+            if (context.getLocation(liveDataType).file == context.file) {
+                // Quick Fixes can only be applied to current file
+                fixes.add(
+                    fix().name("Change `LiveData` type to nullable")
+                        .replace().with("?").range(context.getLocation(liveDataType)).end().build()
+                )
+            }
+            val argument = node.valueArguments[0]
+            if (argument.isNullLiteral()) {
+                // Don't report null!! quick fix.
+                checkNullability(
+                    context,
+                    argument,
+                    "Cannot set non-nullable LiveData value to `null`",
+                    fixes
+                )
+            } else if (argument.isNullable()) {
+                fixes.add(
+                    fix().name("Add non-null asserted (!!) call")
+                        .replace().with("!!").range(context.getLocation(argument)).end().build()
+                )
+                checkNullability(context, argument, "Expected non-nullable value", fixes)
+            }
+        }
+    }
+
     /**
      * Reports a lint error at [element]'s location with message and quick fixes.
      *
@@ -177,7 +204,7 @@
      * @param message The error message to report.
      * @param fixes The Lint Fixes to report.
      */
-    private fun report(
+    private fun checkNullability(
         context: JavaContext,
         element: UElement,
         message: String,
diff --git a/lifecycle/lifecycle-livedata-core-ktx-lint/src/test/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetectorTest.kt b/lifecycle/lifecycle-livedata-core-ktx-lint/src/test/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetectorTest.kt
index 37eb2af..bbb2490 100644
--- a/lifecycle/lifecycle-livedata-core-ktx-lint/src/test/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetectorTest.kt
+++ b/lifecycle/lifecycle-livedata-core-ktx-lint/src/test/java/androidx/lifecycle/lint/NonNullableMutableLiveDataDetectorTest.kt
@@ -221,6 +221,54 @@
     }
 
     @Test
+    fun nullLiteralFailMultipleFieldsDifferentNullability() {
+        check(
+            kotlin(
+                """
+                package com.example
+
+                import androidx.lifecycle.MutableLiveData
+
+                val liveDataField = MutableLiveData<Boolean>()
+                val secondLiveDataField = MutableLiveData<String?>()
+
+                fun foo() {
+                    liveDataField.value = false
+                    secondLiveDataField.value = null
+                }
+            """
+            ).indented()
+        ).expectClean()
+    }
+
+    @Test
+    fun nullLiteralFailMultipleAssignment() {
+        check(
+            kotlin(
+                """
+                package com.example
+
+                import androidx.lifecycle.MutableLiveData
+
+                val liveDataField = MutableLiveData<Boolean>()
+
+                fun foo() {
+                    liveDataField.value = false
+                    liveDataField.value = null
+                }
+            """
+            ).indented()
+        ).expect(
+            """
+src/com/example/test.kt:9: Error: Cannot set non-nullable LiveData value to null [NullSafeMutableLiveData]
+    liveDataField.value = null
+                          ~~~~
+1 errors, 0 warnings
+        """
+        )
+    }
+
+    @Test
     fun nullLiteralFailFieldAndIgnore() {
         check(
             kotlin(
@@ -389,4 +437,166 @@
         """
         )
     }
+
+    @Test
+    fun differentClassSameFieldTestFirstNull() {
+        check(
+            kotlin(
+                """
+                package com.example
+
+                import androidx.lifecycle.MutableLiveData
+
+                class MyClass1 {
+                    val liveDataField = MutableLiveData<Boolean>()
+
+                    fun foo() {
+                        liveDataField.value = null
+                    }
+                }
+            """
+            ).indented(),
+            kotlin(
+                """
+                package com.example
+
+                import androidx.lifecycle.MutableLiveData
+
+                class MyClass2 {
+                    val liveDataField = MutableLiveData<Boolean>()
+
+                    fun foo() {
+                        liveDataField.value = false
+                    }
+                }
+            """
+            ).indented()
+        ).expect(
+            """
+src/com/example/MyClass1.kt:9: Error: Cannot set non-nullable LiveData value to null [NullSafeMutableLiveData]
+        liveDataField.value = null
+                              ~~~~
+1 errors, 0 warnings
+        """
+        ).expectFixDiffs(
+            """
+Fix for src/com/example/MyClass1.kt line 9: Change `LiveData` type to nullable:
+@@ -6 +6
+-     val liveDataField = MutableLiveData<Boolean>()
++     val liveDataField = MutableLiveData<Boolean?>()
+        """
+        )
+    }
+
+    @Test
+    fun differentClassSameFieldTestSecondNull() {
+        check(
+            kotlin(
+                """
+                package com.example
+
+                import androidx.lifecycle.MutableLiveData
+
+                class MyClass1 {
+                    val liveDataField = MutableLiveData<Boolean>()
+
+                    fun foo() {
+                        liveDataField.value = false
+                    }
+                }
+            """
+            ).indented(),
+            kotlin(
+                """
+                package com.example
+
+                import androidx.lifecycle.MutableLiveData
+
+                class MyClass2 {
+                    val liveDataField = MutableLiveData<Boolean>()
+
+                    fun foo() {
+                        liveDataField.value = null
+                    }
+                }
+            """
+            ).indented()
+        ).expect(
+            """
+src/com/example/MyClass2.kt:9: Error: Cannot set non-nullable LiveData value to null [NullSafeMutableLiveData]
+        liveDataField.value = null
+                              ~~~~
+1 errors, 0 warnings
+        """
+        ).expectFixDiffs(
+            """
+Fix for src/com/example/MyClass2.kt line 9: Change `LiveData` type to nullable:
+@@ -6 +6
+-     val liveDataField = MutableLiveData<Boolean>()
++     val liveDataField = MutableLiveData<Boolean?>()
+        """
+        )
+    }
+
+    @Test
+    fun nestedClassSameFieldTest() {
+        check(
+            kotlin(
+                """
+                package com.example
+
+                import androidx.lifecycle.MutableLiveData
+
+                class MyClass1 {
+                    val liveDataField = MutableLiveData<Boolean>()
+
+                    fun foo() {
+                        liveDataField.value = false
+                    }
+
+                    class MyClass2 {
+                        val liveDataField = MutableLiveData<Boolean>()
+
+                        fun foo() {
+                            liveDataField.value = null
+                        }
+                    }
+                }
+            """
+            ).indented()
+        ).expect(
+            """
+src/com/example/MyClass1.kt:16: Error: Cannot set non-nullable LiveData value to null [NullSafeMutableLiveData]
+            liveDataField.value = null
+                                  ~~~~
+1 errors, 0 warnings
+        """
+        ).expectFixDiffs(
+            """
+Fix for src/com/example/MyClass1.kt line 16: Change `LiveData` type to nullable:
+@@ -13 +13
+-         val liveDataField = MutableLiveData<Boolean>()
++         val liveDataField = MutableLiveData<Boolean?>()
+        """
+        )
+    }
+
+    @Test
+    fun objectLiveData() {
+        check(
+            kotlin(
+                """
+                package com.example
+
+                import androidx.lifecycle.LiveData
+
+                val foo = object : LiveData<Int>() {
+                    private fun bar() {
+                        value = 0
+                    }
+                }
+            """
+            ).indented()
+        ).expectClean()
+    }
 }
diff --git a/lifecycle/lifecycle-runtime-ktx/api/current.txt b/lifecycle/lifecycle-runtime-ktx/api/current.txt
index cd4e431..daba2bb 100644
--- a/lifecycle/lifecycle-runtime-ktx/api/current.txt
+++ b/lifecycle/lifecycle-runtime-ktx/api/current.txt
@@ -1,6 +1,10 @@
 // Signature format: 4.0
 package androidx.lifecycle {
 
+  public final class FlowExtKt {
+    method public static <T> kotlinx.coroutines.flow.Flow<T> flowWithLifecycle(kotlinx.coroutines.flow.Flow<? extends T>, androidx.lifecycle.Lifecycle lifecycle, optional androidx.lifecycle.Lifecycle.State minActiveState);
+  }
+
   public abstract class LifecycleCoroutineScope implements kotlinx.coroutines.CoroutineScope {
     method public final kotlinx.coroutines.Job launchWhenCreated(kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
     method public final kotlinx.coroutines.Job launchWhenResumed(kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
@@ -30,8 +34,8 @@
   }
 
   public final class RepeatOnLifecycleKt {
-    method public static kotlinx.coroutines.Job repeatOnLifecycle(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State state, optional kotlin.coroutines.CoroutineContext coroutineContext, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
-    method public static kotlinx.coroutines.Job repeatOnLifecycle(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, optional kotlin.coroutines.CoroutineContext coroutineContext, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+    method public static kotlinx.coroutines.Job addRepeatingJob(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State state, optional kotlin.coroutines.CoroutineContext coroutineContext, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+    method public static suspend Object? repeatOnLifecycle(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
   }
 
   public final class ViewKt {
diff --git a/lifecycle/lifecycle-runtime-ktx/api/public_plus_experimental_current.txt b/lifecycle/lifecycle-runtime-ktx/api/public_plus_experimental_current.txt
index cd4e431..daba2bb 100644
--- a/lifecycle/lifecycle-runtime-ktx/api/public_plus_experimental_current.txt
+++ b/lifecycle/lifecycle-runtime-ktx/api/public_plus_experimental_current.txt
@@ -1,6 +1,10 @@
 // Signature format: 4.0
 package androidx.lifecycle {
 
+  public final class FlowExtKt {
+    method public static <T> kotlinx.coroutines.flow.Flow<T> flowWithLifecycle(kotlinx.coroutines.flow.Flow<? extends T>, androidx.lifecycle.Lifecycle lifecycle, optional androidx.lifecycle.Lifecycle.State minActiveState);
+  }
+
   public abstract class LifecycleCoroutineScope implements kotlinx.coroutines.CoroutineScope {
     method public final kotlinx.coroutines.Job launchWhenCreated(kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
     method public final kotlinx.coroutines.Job launchWhenResumed(kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
@@ -30,8 +34,8 @@
   }
 
   public final class RepeatOnLifecycleKt {
-    method public static kotlinx.coroutines.Job repeatOnLifecycle(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State state, optional kotlin.coroutines.CoroutineContext coroutineContext, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
-    method public static kotlinx.coroutines.Job repeatOnLifecycle(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, optional kotlin.coroutines.CoroutineContext coroutineContext, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+    method public static kotlinx.coroutines.Job addRepeatingJob(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State state, optional kotlin.coroutines.CoroutineContext coroutineContext, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+    method public static suspend Object? repeatOnLifecycle(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
   }
 
   public final class ViewKt {
diff --git a/lifecycle/lifecycle-runtime-ktx/api/restricted_current.txt b/lifecycle/lifecycle-runtime-ktx/api/restricted_current.txt
index 07c02b7..72594e0 100644
--- a/lifecycle/lifecycle-runtime-ktx/api/restricted_current.txt
+++ b/lifecycle/lifecycle-runtime-ktx/api/restricted_current.txt
@@ -1,6 +1,10 @@
 // Signature format: 4.0
 package androidx.lifecycle {
 
+  public final class FlowExtKt {
+    method public static <T> kotlinx.coroutines.flow.Flow<T> flowWithLifecycle(kotlinx.coroutines.flow.Flow<? extends T>, androidx.lifecycle.Lifecycle lifecycle, optional androidx.lifecycle.Lifecycle.State minActiveState);
+  }
+
   public abstract class LifecycleCoroutineScope implements kotlinx.coroutines.CoroutineScope {
     method public final kotlinx.coroutines.Job launchWhenCreated(kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
     method public final kotlinx.coroutines.Job launchWhenResumed(kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
@@ -30,8 +34,8 @@
   }
 
   public final class RepeatOnLifecycleKt {
-    method public static kotlinx.coroutines.Job repeatOnLifecycle(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State state, optional kotlin.coroutines.CoroutineContext coroutineContext, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
-    method public static kotlinx.coroutines.Job repeatOnLifecycle(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, optional kotlin.coroutines.CoroutineContext coroutineContext, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+    method public static kotlinx.coroutines.Job addRepeatingJob(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State state, optional kotlin.coroutines.CoroutineContext coroutineContext, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+    method public static suspend Object? repeatOnLifecycle(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
   }
 
   public final class ViewKt {
diff --git a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/RepeatOnLifecycleTest.kt b/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/AddRepeatingJobTest.kt
similarity index 70%
rename from lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/RepeatOnLifecycleTest.kt
rename to lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/AddRepeatingJobTest.kt
index 2a8ae8e..d7a0af4 100644
--- a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/RepeatOnLifecycleTest.kt
+++ b/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/AddRepeatingJobTest.kt
@@ -19,7 +19,7 @@
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CancellationException
-import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineExceptionHandler
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
@@ -32,7 +32,7 @@
 import org.junit.Test
 
 @SmallTest
-class RepeatOnLifecycleTest {
+class AddRepeatingJobTest {
 
     private val expectations = Expectations()
     private val owner = FakeLifecycleOwner()
@@ -41,9 +41,11 @@
     fun testBlockRunsWhenCreatedStateIsReached() = runBlocking(Dispatchers.Main) {
         owner.setState(Lifecycle.State.CREATED)
         expectations.expect(1)
-        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.CREATED) {
+
+        val repeatingWorkJob = owner.addRepeatingJob(Lifecycle.State.CREATED) {
             expectations.expect(2)
         }
+
         expectations.expect(3)
         owner.setState(Lifecycle.State.DESTROYED)
         assertThat(repeatingWorkJob.isCompleted).isTrue()
@@ -53,22 +55,25 @@
     fun testBlockRunsWhenStartedStateIsReached() = runBlocking(Dispatchers.Main) {
         owner.setState(Lifecycle.State.CREATED)
         expectations.expect(1)
-        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+
+        val repeatingWorkJob = owner.addRepeatingJob(Lifecycle.State.STARTED) {
             expectations.expect(2)
         }
+
         owner.setState(Lifecycle.State.STARTED)
         expectations.expect(3)
         owner.setState(Lifecycle.State.DESTROYED)
         assertThat(repeatingWorkJob.isCompleted).isTrue()
     }
-
     @Test
     fun testBlockRunsWhenResumedStateIsReached() = runBlocking(Dispatchers.Main) {
         owner.setState(Lifecycle.State.CREATED)
         expectations.expect(1)
-        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+
+        val repeatingWorkJob = owner.addRepeatingJob(Lifecycle.State.RESUMED) {
             expectations.expect(3)
         }
+
         owner.setState(Lifecycle.State.STARTED)
         expectations.expect(2)
         owner.setState(Lifecycle.State.RESUMED)
@@ -76,20 +81,20 @@
         owner.setState(Lifecycle.State.DESTROYED)
         assertThat(repeatingWorkJob.isCompleted).isTrue()
     }
-
     @Test
     fun testBlocksRepeatsExecution() = runBlocking(Dispatchers.Main) {
         owner.setState(Lifecycle.State.CREATED)
         var restarted = false
         expectations.expect(1)
 
-        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+        val repeatingWorkJob = owner.addRepeatingJob(Lifecycle.State.RESUMED) {
             if (!restarted) {
                 expectations.expect(2)
             } else {
                 expectations.expect(5)
             }
         }
+
         owner.setState(Lifecycle.State.RESUMED)
         expectations.expect(3)
         owner.setState(Lifecycle.State.STARTED)
@@ -106,7 +111,8 @@
     fun testBlockIsCancelledWhenLifecycleIsDestroyed() = runBlocking(Dispatchers.Main) {
         owner.setState(Lifecycle.State.RESUMED)
         expectations.expect(1)
-        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+
+        val repeatingWorkJob = owner.addRepeatingJob(Lifecycle.State.STARTED) {
             try {
                 expectations.expect(2)
                 awaitCancellation()
@@ -114,8 +120,10 @@
                 expectations.expect(4)
             }
         }
+
         expectations.expect(3)
         owner.setState(Lifecycle.State.DESTROYED)
+
         yield() // let the cancellation code run before asserting it happened
         expectations.expect(5)
         assertThat(repeatingWorkJob.isCompleted).isTrue()
@@ -125,9 +133,11 @@
     fun testBlockRunsOnSubsequentLifecycleState() = runBlocking(Dispatchers.Main) {
         owner.setState(Lifecycle.State.RESUMED)
         expectations.expect(1)
-        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+
+        val repeatingWorkJob = owner.addRepeatingJob(Lifecycle.State.STARTED) {
             expectations.expect(2)
         }
+
         expectations.expect(3)
         owner.setState(Lifecycle.State.DESTROYED)
         assertThat(repeatingWorkJob.isCompleted).isTrue()
@@ -137,9 +147,11 @@
     fun testBlockDoesNotStartIfLifecycleIsDestroyed() = runBlocking(Dispatchers.Main) {
         owner.setState(Lifecycle.State.DESTROYED)
         expectations.expect(1)
-        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+
+        val repeatingWorkJob = owner.addRepeatingJob(Lifecycle.State.STARTED) {
             expectations.expectUnreached()
         }
+
         expectations.expect(2)
         assertThat(repeatingWorkJob.isCompleted).isTrue()
     }
@@ -148,7 +160,8 @@
     fun testCancellingTheReturnedJobCancelsTheBlock() = runBlocking(Dispatchers.Main) {
         owner.setState(Lifecycle.State.RESUMED)
         expectations.expect(1)
-        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+
+        val repeatingWorkJob = owner.addRepeatingJob(Lifecycle.State.STARTED) {
             try {
                 expectations.expect(2)
                 awaitCancellation()
@@ -156,34 +169,76 @@
                 expectations.expect(4)
             }
         }
+
         expectations.expect(3)
         repeatingWorkJob.cancel()
         yield() // let the cancellation code run before asserting it happened
+
         expectations.expect(5)
         assertThat(repeatingWorkJob.isCancelled).isTrue()
         assertThat(repeatingWorkJob.isCompleted).isTrue()
     }
 
     @Test
-    fun testCancellingACustomJobCancelsTheBlock() = runBlocking(Dispatchers.Main) {
+    fun testCancellingACustomJobCanBeHandled() = runBlocking(Dispatchers.Main) {
         owner.setState(Lifecycle.State.RESUMED)
         expectations.expect(1)
+
         val customJob = Job()
-        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.STARTED, customJob) {
-            try {
-                expectations.expect(2)
-                awaitCancellation()
-            } catch (e: CancellationException) {
-                expectations.expect(4)
+        val repeatingWorkJob = owner.addRepeatingJob(Lifecycle.State.STARTED) {
+            withContext(customJob) {
+                try {
+                    expectations.expect(2)
+                    awaitCancellation()
+                } catch (e: CancellationException) {
+                    expectations.expect(4)
+                }
             }
         }
+
         expectations.expect(3)
         customJob.cancel()
         yield() // let the cancellation code run before asserting it happened
+
         expectations.expect(5)
         assertThat(customJob.isCancelled).isTrue()
         assertThat(customJob.isCompleted).isTrue()
-        assertThat(repeatingWorkJob.isCancelled).isTrue()
+        owner.setState(Lifecycle.State.DESTROYED)
+        assertThat(repeatingWorkJob.isCompleted).isTrue()
+    }
+
+    @Test
+    fun testCancellingACustomJobDoesNotReRunThatBlock() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.CREATED)
+        var restarted = false
+        expectations.expect(1)
+
+        val customJob = Job()
+        val repeatingWorkJob = owner.addRepeatingJob(Lifecycle.State.RESUMED) {
+            if (!restarted) {
+                expectations.expect(2)
+            } else {
+                expectations.expect(6)
+            }
+            withContext(customJob) {
+                if (!restarted) {
+                    expectations.expect(3)
+                } else {
+                    expectations.expectUnreached()
+                }
+            }
+        }
+
+        owner.setState(Lifecycle.State.RESUMED)
+        expectations.expect(4)
+        owner.setState(Lifecycle.State.STARTED)
+        expectations.expect(5)
+
+        customJob.cancel()
+        restarted = true
+        owner.setState(Lifecycle.State.RESUMED)
+        expectations.expect(7)
+        owner.setState(Lifecycle.State.DESTROYED)
         assertThat(repeatingWorkJob.isCompleted).isTrue()
     }
 
@@ -193,45 +248,29 @@
         expectations.expect(1)
 
         var restarted = false
-        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+        val repeatingWorkJob = owner.addRepeatingJob(Lifecycle.State.RESUMED) {
             if (!restarted) {
                 expectations.expect(2)
             } else {
                 expectations.expectUnreached()
             }
         }
+
         expectations.expect(3)
         repeatingWorkJob.cancel()
         assertThat(repeatingWorkJob.isCancelled).isTrue()
         assertThat(repeatingWorkJob.isCompleted).isTrue()
-
         owner.setState(Lifecycle.State.STARTED)
+
         restarted = true
         expectations.expect(4)
         owner.setState(Lifecycle.State.RESUMED)
         yield() // Block shouldn't restart
+
         expectations.expect(5)
         owner.setState(Lifecycle.State.DESTROYED)
     }
 
-    @OptIn(ExperimentalCoroutinesApi::class, ExperimentalStdlibApi::class)
-    @Test
-    fun testBlockRunsWhenUsingCustomDispatchers() = runBlocking(Dispatchers.Main) {
-        owner.setState(Lifecycle.State.CREATED)
-        expectations.expect(1)
-
-        val testDispatcher = TestCoroutineDispatcher()
-        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.CREATED, testDispatcher) {
-            // testDispatcher is ignored. This still runs on Dispatchers.Main
-            assertThat(coroutineContext[CoroutineDispatcher]).isEqualTo(Dispatchers.Main)
-            expectations.expect(2)
-        }
-        expectations.expect(3)
-        owner.setState(Lifecycle.State.DESTROYED)
-        assertThat(repeatingWorkJob.isCompleted).isTrue()
-        testDispatcher.cleanupTestCoroutines()
-    }
-
     @OptIn(ExperimentalCoroutinesApi::class)
     @Test
     fun testBlockRunsWhenLogicUsesWithContext() = runBlocking(Dispatchers.Main) {
@@ -240,7 +279,7 @@
 
         val testDispatcher = TestCoroutineDispatcher().apply {
             runBlockingTest {
-                owner.repeatOnLifecycle(Lifecycle.State.CREATED) {
+                owner.addRepeatingJob(Lifecycle.State.CREATED) {
                     withContext(this@apply) {
                         expectations.expect(2)
                     }
@@ -254,26 +293,31 @@
     }
 
     @Test
-    fun testAddRepeatingWorkFailsWithInitializedState() = runBlocking(Dispatchers.Main) {
-        try {
-            owner.repeatOnLifecycle(Lifecycle.State.INITIALIZED) {
-                // IllegalArgumentException expected
-            }
-        } catch (e: Throwable) {
-            assertThat(e is IllegalArgumentException).isTrue()
+    fun testBlockDoesNotStartWithDestroyedState() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.STARTED)
+        expectations.expect(1)
+
+        val repeatingWorkJob = owner.addRepeatingJob(Lifecycle.State.DESTROYED) {
+            expectations.expectUnreached()
         }
-        Unit // runBlocking tries to return the result of the try expression, using Unit instead
+
+        expectations.expect(2)
+        owner.setState(Lifecycle.State.DESTROYED)
+        assertThat(repeatingWorkJob.isCompleted).isTrue()
     }
 
     @Test
-    fun testAddRepeatingWorkFailsWithDestroyedState() = runBlocking(Dispatchers.Main) {
-        try {
-            owner.repeatOnLifecycle(Lifecycle.State.DESTROYED) {
-                // IllegalArgumentException expected
-            }
-        } catch (e: Throwable) {
-            assertThat(e is IllegalArgumentException).isTrue()
+    fun testAddRepeatingJobFailsWithInitializedState() = runBlocking(Dispatchers.Main) {
+        val exceptions: MutableList<Throwable> = mutableListOf()
+        val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception ->
+            exceptions.add(exception)
         }
-        Unit // runBlocking tries to return the result of the try expression, using Unit instead
+
+        owner.addRepeatingJob(Lifecycle.State.INITIALIZED, coroutineExceptionHandler) {
+            // IllegalArgumentException expected
+        }
+
+        assertThat(exceptions[0]).isInstanceOf(IllegalArgumentException::class.java)
+        assertThat(exceptions).hasSize(1)
     }
 }
diff --git a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/FlowWithLifecycleTest.kt b/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/FlowWithLifecycleTest.kt
new file mode 100644
index 0000000..1020f5d
--- /dev/null
+++ b/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/FlowWithLifecycleTest.kt
@@ -0,0 +1,238 @@
+/*
+ * 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.lifecycle
+
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.take
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.Test
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+class FlowWithLifecycleTest {
+    private val owner = FakeLifecycleOwner()
+
+    @Test
+    fun testFiniteFlowCompletes() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.CREATED)
+        val result = flowOf(1, 2, 3)
+            .flowWithLifecycle(owner.lifecycle, Lifecycle.State.CREATED)
+            .take(3)
+            .toList()
+        assertThat(result).containsExactly(1, 2, 3).inOrder()
+        owner.setState(Lifecycle.State.DESTROYED)
+    }
+
+    @Test
+    fun testFlowStartsInSubsequentLifecycleState() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.RESUMED)
+        val result = flowOf(1, 2, 3)
+            .flowWithLifecycle(owner.lifecycle, Lifecycle.State.CREATED)
+            .take(3)
+            .toList()
+        assertThat(result).containsExactly(1, 2, 3).inOrder()
+        owner.setState(Lifecycle.State.DESTROYED)
+    }
+
+    @Test
+    fun testFlowDoesNotCollectIfLifecycleIsDestroyed() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.DESTROYED)
+        val result = flowOf(1, 2, 3)
+            .flowWithLifecycle(owner.lifecycle, Lifecycle.State.RESUMED)
+            .take(3)
+            .toList()
+        assertThat(result.size).isEqualTo(0)
+    }
+
+    @Test
+    fun testCollectionRestartsWithFlowThatCompletes() = runBlocking(Dispatchers.Main) {
+        assertFlowCollectsAgainOnRestart(
+            flowOf(1, 2),
+            expectedItemsBeforeRestarting = listOf(1, 2),
+            expectedItemsAfterRestarting = listOf(1, 2, 1, 2)
+        )
+    }
+
+    @Test
+    fun testCollectionRestartsWithFlowThatDoesNotComplete() = runBlocking(Dispatchers.Main) {
+        assertFlowCollectsAgainOnRestart(
+            flow {
+                emit(1)
+                emit(2)
+                delay(10000L)
+            },
+            expectedItemsBeforeRestarting = listOf(1, 2),
+            expectedItemsAfterRestarting = listOf(1, 2, 1, 2)
+        )
+    }
+
+    @Test
+    fun testCollectionRestartsWithAHotFlow() = runBlocking(Dispatchers.Main) {
+        val sharedFlow = MutableSharedFlow<Int>()
+        assertFlowCollectsAgainOnRestart(
+            sharedFlow,
+            expectedItemsBeforeRestarting = listOf(1, 2),
+            expectedItemsAfterRestarting = listOf(1, 2, 4),
+            beforeRestart = {
+                sharedFlow.emit(1)
+                sharedFlow.emit(2)
+            },
+            onRestart = { sharedFlow.emit(3) },
+            afterRestart = { sharedFlow.emit(4) }
+        )
+    }
+
+    @Test
+    fun testCancellingCoroutineDoesNotGetUpdates() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.STARTED)
+        val sharedFlow = MutableSharedFlow<Int>()
+        val resultList = mutableListOf<Int>()
+        val job = launch(Dispatchers.Main.immediate) {
+            sharedFlow
+                .flowWithLifecycle(owner.lifecycle, Lifecycle.State.RESUMED)
+                .collect { resultList.add(it) }
+        }
+        owner.setState(Lifecycle.State.RESUMED)
+        sharedFlow.emit(1)
+        sharedFlow.emit(2)
+        yield()
+        assertThat(resultList).containsExactly(1, 2).inOrder()
+        // Lifecycle is cancelled
+        job.cancel()
+        yield()
+        sharedFlow.emit(3)
+        yield()
+        // No more items are received
+        assertThat(resultList).containsExactly(1, 2).inOrder()
+        owner.setState(Lifecycle.State.DESTROYED)
+    }
+
+    @Test
+    fun testDestroyedLifecycleDoesNotGetUpdates() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.STARTED)
+        val sharedFlow = MutableSharedFlow<Int>()
+        val resultList = mutableListOf<Int>()
+        launch(Dispatchers.Main.immediate) {
+            sharedFlow
+                .flowWithLifecycle(owner.lifecycle, Lifecycle.State.RESUMED)
+                .collect { resultList.add(it) }
+        }
+        owner.setState(Lifecycle.State.RESUMED)
+        sharedFlow.emit(1)
+        sharedFlow.emit(2)
+        yield()
+        assertThat(resultList).containsExactly(1, 2).inOrder()
+        // Lifecycle is cancelled
+        owner.setState(Lifecycle.State.DESTROYED)
+        sharedFlow.emit(3)
+        yield()
+        // No more items are received
+        assertThat(resultList).containsExactly(1, 2).inOrder()
+    }
+
+    @Test
+    fun testWithLaunchIn() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.STARTED)
+        val resultList = mutableListOf<Int>()
+        flowOf(1, 2, 3)
+            .flowWithLifecycle(owner.lifecycle)
+            .onEach { resultList.add(it) }
+            .launchIn(owner.lifecycleScope)
+        assertThat(resultList).containsExactly(1, 2, 3).inOrder()
+        // Lifecycle is cancelled
+        owner.setState(Lifecycle.State.DESTROYED)
+    }
+
+    @Test
+    fun testExtensionFailsWithInitializedState() = runBlocking(Dispatchers.Main) {
+        try {
+            flowOf(1, 2, 3)
+                .flowWithLifecycle(owner.lifecycle, Lifecycle.State.INITIALIZED)
+                .take(3)
+                .toList()
+        } catch (e: Throwable) {
+            assertThat(e is IllegalArgumentException).isTrue()
+        }
+        Unit // tries to return the result of the try expression, using Unit instead
+    }
+
+    @Test
+    fun testExtensionDoesNotCollectInDestroyedState() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.STARTED)
+        val resultList = mutableListOf<Int>()
+        launch(Dispatchers.Main.immediate) {
+            flowOf(1, 2, 3)
+                .flowWithLifecycle(owner.lifecycle, Lifecycle.State.DESTROYED)
+                .collect { resultList.add(it) }
+        }
+        assertThat(resultList).isEmpty()
+        // Lifecycle is cancelled
+        owner.setState(Lifecycle.State.DESTROYED)
+    }
+
+    private suspend fun assertFlowCollectsAgainOnRestart(
+        flowUnderTest: Flow<Int>,
+        expectedItemsBeforeRestarting: List<Int>,
+        expectedItemsAfterRestarting: List<Int>,
+        beforeRestart: suspend () -> Unit = { },
+        onRestart: suspend () -> Unit = { },
+        afterRestart: suspend () -> Unit = { }
+    ) = coroutineScope {
+        owner.setState(Lifecycle.State.STARTED)
+
+        val resultList = mutableListOf<Int>()
+        launch(Dispatchers.Main.immediate) {
+            flowUnderTest
+                .flowWithLifecycle(owner.lifecycle, Lifecycle.State.RESUMED)
+                .collect { resultList.add(it) }
+        }
+        assertThat(resultList.size).isEqualTo(0)
+        owner.setState(Lifecycle.State.RESUMED)
+
+        beforeRestart()
+        yield()
+        assertThat(resultList).containsExactlyElementsIn(expectedItemsBeforeRestarting).inOrder()
+        // Flow collection cancels
+        owner.setState(Lifecycle.State.STARTED)
+
+        onRestart()
+        yield()
+        assertThat(resultList).containsExactlyElementsIn(expectedItemsBeforeRestarting).inOrder()
+        // Flow collection resumes
+        owner.setState(Lifecycle.State.RESUMED)
+
+        afterRestart()
+        yield()
+        assertThat(resultList).containsExactlyElementsIn(expectedItemsAfterRestarting).inOrder()
+        owner.setState(Lifecycle.State.DESTROYED)
+    }
+}
diff --git a/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/FlowExt.kt b/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/FlowExt.kt
new file mode 100644
index 0000000..bfaf4be
--- /dev/null
+++ b/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/FlowExt.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.lifecycle
+
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.launchIn
+
+/**
+ * Flow operator that emits values from `this` upstream Flow when the [lifecycle] is
+ * at least at [minActiveState] state. The emissions will be stopped when the lifecycle state
+ * falls below [minActiveState] state.
+ *
+ * The flow will automatically start and cancel collecting from `this` upstream flow as the
+ * [lifecycle] moves in and out of the target state.
+ *
+ * If [this] upstream Flow completes emitting items, `flowWithLifecycle` will trigger the flow
+ * collection again when the [minActiveState] state is reached.
+ *
+ * This is NOT a terminal operator. This operator is usually followed by [collect], or
+ * [onEach] and [launchIn] to process the emitted values.
+ *
+ * Note: this operator creates a hot flow that only closes when the [lifecycle] is destroyed or
+ * the coroutine that collects from the flow is cancelled.
+ *
+ * ```
+ * class MyActivity : AppCompatActivity() {
+ *     override fun onCreate(savedInstanceState: Bundle?) {
+ *         /* ... */
+ *         // Launches a coroutine that collects items from a flow when the Activity
+ *         // is at least started. It will automatically cancel when the activity is stopped and
+ *         // start collecting again whenever it's started again.
+ *         lifecycleScope.launch {
+ *             flow
+ *                 .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
+ *                 .collect {
+ *                     // Consume flow emissions
+ *                  }
+ *         }
+ *     }
+ * }
+ * ```
+ *
+ * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
+ * parameter will throw an [IllegalArgumentException].
+ *
+ * Tip: If multiple flows need to be collected using `flowWithLifecycle`, consider using
+ * the [LifecycleOwner.addRepeatingJob] API to collect from all of them using a different
+ * [launch] per flow instead. This will be more efficient as only one [LifecycleObserver] will be
+ * added to the [lifecycle] instead of one per flow.
+ *
+ * @param lifecycle The [Lifecycle] where the restarting collecting from `this` flow work will be
+ * kept alive.
+ * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
+ * collection will stop if the lifecycle falls below that state, and will restart if it's in that
+ * state again.
+ * @return [Flow] that only emits items from `this` upstream flow when the [lifecycle] is at
+ * least in the [minActiveState].
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+public fun <T> Flow<T>.flowWithLifecycle(
+    lifecycle: Lifecycle,
+    minActiveState: Lifecycle.State = Lifecycle.State.STARTED
+): Flow<T> = callbackFlow {
+    lifecycle.repeatOnLifecycle(minActiveState) {
+        [email protected] {
+            send(it)
+        }
+    }
+    close()
+}
diff --git a/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/RepeatOnLifecycle.kt b/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/RepeatOnLifecycle.kt
index 92e1234..4e14e28 100644
--- a/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/RepeatOnLifecycle.kt
+++ b/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/RepeatOnLifecycle.kt
@@ -16,40 +16,30 @@
 
 package androidx.lifecycle
 
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withContext
 import kotlin.coroutines.CoroutineContext
 import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.coroutines.resume
 
 /**
- * Launches and runs the given [block] in a coroutine that executes the [block] on the
- * main thread when this [LifecycleOwner]'s [Lifecycle] is at least at [state].
- * The launched coroutine will be cancelled when the lifecycle state falls below [state].
+ * Launches and runs the given [block] in a coroutine when `this` [LifecycleOwner]'s [Lifecycle]
+ * is at least at [state]. The launched coroutine will be cancelled when the lifecycle state falls
+ * below [state].
  *
  * The [block] will cancel and re-launch as the lifecycle moves in and out of the target state.
  * To permanently remove the work from the lifecycle, [Job.cancel] the returned [Job].
  *
- * [Lifecycle] is bound to the Android **main thread** and the lifecycle state is only
- * guaranteed to be valid and consistent on that main thread. [block] always runs on
- * [Dispatchers.Main] and launches when the lifecycle [state] is first reached.
- * **This overrides any [CoroutineDispatcher] specified in [coroutineContext].**
- *
- * An active coroutine is a child [Job] of any job present in [coroutineContext]. This returned
- * [Job] is the parent of any currently running [block] and will **complete** when the [Lifecycle]
- * is [destroyed][Lifecycle.Event.ON_DESTROY]. To perform an action when a [RepeatingWorkObserver]
- * is completed or cancelled, see [Job.join] or [Job.invokeOnCompletion].
- *
  * ```
  * // Runs the block of code in a coroutine when the lifecycleOwner is at least STARTED.
  * // The coroutine will be cancelled when the ON_STOP event happens and will restart executing
  * // if the lifecycleOwner's lifecycle receives the ON_START event again.
- * lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ * lifecycleOwner.addRepeatingJob(Lifecycle.State.STARTED) {
  *     uiStateFlow.collect { uiState ->
  *         updateUi(uiState)
  *     }
@@ -60,141 +50,94 @@
  * example, `onCreate` in an Activity, or `onViewCreated` in a Fragment. Otherwise, multiple
  * repeating jobs doing the same could be registered and be executed at the same time.
  *
- * Warning: [Lifecycle.State.DESTROYED] and [Lifecycle.State.INITIALIZED] are not allowed in this
- * API. Passing those as a parameter will throw an [IllegalArgumentException].
+ * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
+ * parameter will throw an [IllegalArgumentException].
  *
- * @param state [Lifecycle.State] in which the coroutine starts. That coroutine will cancel
- * if the lifecycle falls below that state, and will restart if it's in that state again.
- * @param coroutineContext [CoroutineContext] used to execute [block]. Note that its
- * [CoroutineDispatcher] will be replaced by [Dispatchers.Main].
+ * @see Lifecycle.repeatOnLifecycle for details
+ *
+ * @param state [Lifecycle.State] in which the coroutine running [block] starts. That coroutine
+ * will cancel if the lifecycle falls below that state, and will restart if it's in that state
+ * again.
+ * @param coroutineContext [CoroutineContext] used to execute [block].
  * @param block The block to run when the lifecycle is at least in [state] state.
  * @return [Job] to manage the repeating work.
  */
-public fun LifecycleOwner.repeatOnLifecycle(
+public fun LifecycleOwner.addRepeatingJob(
     state: Lifecycle.State,
     coroutineContext: CoroutineContext = EmptyCoroutineContext,
     block: suspend CoroutineScope.() -> Unit
-): Job = lifecycle.repeatOnLifecycle(state, coroutineContext, block)
-
-/**
- * Launches and runs the given [block] in a coroutine that executes the [block] on the
- * main thread when this [Lifecycle] is at least at [state].
- * The launched coroutine will be cancelled when the lifecycle state falls below [state].
- *
- * The [block] will cancel and re-launch as the lifecycle moves in and out of the target state.
- * To permanently remove the work from the lifecycle, [Job.cancel] the returned [Job].
- *
- * [Lifecycle] is bound to the Android **main thread** and the lifecycle state is only
- * guaranteed to be valid and consistent on that main thread. [block] always runs on
- * [Dispatchers.Main] and launches when the lifecycle [state] is first reached.
- * **This overrides any [CoroutineDispatcher] specified in [coroutineContext].**
- *
- * An active coroutine is a child [Job] of any job present in [coroutineContext]. This returned
- * [Job] is the parent of any currently running [block] and will **complete** when the [Lifecycle]
- * is [destroyed][Lifecycle.Event.ON_DESTROY]. To perform an action when a [RepeatingWorkObserver]
- * is completed or cancelled, see [Job.join] or [Job.invokeOnCompletion].
- *
- * ```
- * class MyActivity : AppCompatActivity() {
- *     override fun onCreate(savedInstanceState: Bundle?) {
- *         /* ... */
- *         // Runs the block of code in a coroutine when the lifecycle is at least STARTED.
- *         // The coroutine will be cancelled when the ON_STOP event happens and will restart
- *         // executing if the lifecycle receives the ON_START event again.
- *         lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
- *             uiStateFlow.collect { uiState ->
- *                 updateUi(uiState)
- *             }
- *         }
- *     }
- * }
- * ```
- *
- * The best practice is to call this function in the lifecycle callback when the View gets
- * created. For example, `onCreate` in an Activity, or `onViewCreated` in a Fragment. Otherwise,
- * multiple repeating jobs doing the same could be registered and be executed at the same time.
- *
- * Warning: [Lifecycle.State.DESTROYED] and [Lifecycle.State.INITIALIZED] are not allowed in this
- * API. Passing those as a parameter will throw an [IllegalArgumentException].
- *
- * @param state [Lifecycle.State] in which the coroutine starts. That coroutine will cancel
- * if the lifecycle falls below that state, and will restart if it's in that state again.
- * @param coroutineContext [CoroutineContext] used to execute [block]. Note that its
- * [CoroutineDispatcher] will be replaced by [Dispatchers.Main].
- * @param block The block to run when the lifecycle is at least in [state].
- * @return [Job] to manage the repeating work.
- */
-public fun Lifecycle.repeatOnLifecycle(
-    state: Lifecycle.State,
-    coroutineContext: CoroutineContext = EmptyCoroutineContext,
-    block: suspend CoroutineScope.() -> Unit
-): Job {
-    if (currentState === Lifecycle.State.DESTROYED) {
-        // Fast-path! As the work would immediately complete, return a completed Job
-        // to avoid extra allocations and adding/removing observers
-        return Job().apply { complete() }
-    }
-
-    return RepeatingWorkObserver(this, state, coroutineContext + Dispatchers.Main, block)
-        .also { observer ->
-            // Immediately add the LifecycleEventObserver to ensure that RepeatingWorkObserver Job's
-            // `invokeOnCompletion` that removes the observer doesn't happen before this in the case
-            // of a parent job cancelled early or an ON_DESTROY completing the observer
-            observer.launch(Dispatchers.Main.immediate) {
-                addObserver(observer)
-            }
-        }.job
+): Job = lifecycleScope.launch(coroutineContext) {
+    lifecycle.repeatOnLifecycle(state, block)
 }
 
 /**
- * [LifecycleEventObserver] that executes work periodically when the Lifecycle reaches
- * certain State.
+ * Runs the given [block] in a new coroutine when `this` [Lifecycle] is at least at [state] and
+ * suspends the execution until `this` [Lifecycle] is [Lifecycle.State.DESTROYED].
+ *
+ * The [block] will cancel and re-launch as the lifecycle moves in and out of the target state.
+ *
+ * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
+ * parameter will throw an [IllegalArgumentException].
+ *
+ * @param state [Lifecycle.State] in which `block` runs in a new coroutine. That coroutine
+ * will cancel if the lifecycle falls below that state, and will restart if it's in that state
+ * again.
+ * @param block The block to run when the lifecycle is at least in [state] state.
  */
-private class RepeatingWorkObserver(
-    private val lifecycle: Lifecycle,
+public suspend fun Lifecycle.repeatOnLifecycle(
     state: Lifecycle.State,
-    coroutineContext: CoroutineContext = EmptyCoroutineContext,
-    private val block: suspend CoroutineScope.() -> Unit,
-) : LifecycleEventObserver, CoroutineScope {
-
-    init {
-        if (state === Lifecycle.State.INITIALIZED || state === Lifecycle.State.DESTROYED) {
-            throw IllegalArgumentException(
-                "RepeatingWorkObserver cannot start work with lifecycle states that are " +
-                    "at least INITIALIZED or DESTROYED."
-            )
-        }
+    block: suspend CoroutineScope.() -> Unit
+) {
+    require(state !== Lifecycle.State.INITIALIZED) {
+        "repeatOnLifecycle cannot start work with the INITIALIZED lifecycle state."
     }
 
-    private val startWorkEvent = Lifecycle.Event.upTo(state)
-    private val cancelWorkEvent = Lifecycle.Event.downFrom(state)
-
-    // Exposing this job to enable cancelling RepeatingWorkObserver from the outside
-    val job = Job(coroutineContext[Job]).apply { invokeOnCompletion { removeSelf() } }
-    override val coroutineContext = coroutineContext + job
-
-    private var launchedJob: Job? = null
-
-    @OptIn(ExperimentalCoroutinesApi::class)
-    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
-        if (event == startWorkEvent) {
-            launchedJob = launch(start = CoroutineStart.UNDISPATCHED, block = block)
-            return
-        }
-        if (event == cancelWorkEvent) {
-            launchedJob?.cancel()
-            launchedJob = null
-        }
-        if (event == Lifecycle.Event.ON_DESTROY) {
-            job.complete()
-        }
+    if (currentState === Lifecycle.State.DESTROYED) {
+        return
     }
 
-    private fun removeSelf() {
-        if (Dispatchers.Main.immediate.isDispatchNeeded(EmptyCoroutineContext)) {
-            GlobalScope.launch(Dispatchers.Main.immediate) {
-                lifecycle.removeObserver(this@RepeatingWorkObserver)
+    coroutineScope {
+        withContext(Dispatchers.Main.immediate) {
+            // Check the current state of the lifecycle as the previous check is not guaranteed
+            // to be done on the main thread.
+            if (currentState === Lifecycle.State.DESTROYED) return@withContext
+
+            // Instance of the running repeating coroutine
+            var launchedJob: Job? = null
+
+            // Registered observer
+            var observer: LifecycleEventObserver? = null
+
+            try {
+                // Suspend the coroutine until the lifecycle is destroyed or
+                // the coroutine is cancelled
+                suspendCancellableCoroutine<Unit> { cont ->
+                    // Lifecycle observers that executes `block` when the lifecycle reaches certain state, and
+                    // cancels when it moves falls below that state.
+                    val startWorkEvent = Lifecycle.Event.upTo(state)
+                    val cancelWorkEvent = Lifecycle.Event.downFrom(state)
+                    observer = LifecycleEventObserver { _, event ->
+                        if (event == startWorkEvent) {
+                            // Launch the repeating work preserving the calling context
+                            launchedJob = [email protected](block = block)
+                            return@LifecycleEventObserver
+                        }
+                        if (event == cancelWorkEvent) {
+                            launchedJob?.cancel()
+                            launchedJob = null
+                        }
+                        if (event == Lifecycle.Event.ON_DESTROY) {
+                            cont.resume(Unit)
+                        }
+                    }
+                    [email protected](observer as LifecycleEventObserver)
+                }
+            } finally {
+                launchedJob?.cancel()
+                observer?.let {
+                    [email protected](it)
+                }
             }
-        } else lifecycle.removeObserver(this@RepeatingWorkObserver)
+        }
     }
 }
diff --git a/lifecycle/settings.gradle b/lifecycle/settings.gradle
index c700624..84747cf 100644
--- a/lifecycle/settings.gradle
+++ b/lifecycle/settings.gradle
@@ -24,7 +24,8 @@
     if (name == ":annotation:annotation") return true
     if (name == ":internal-testutils-runtime") return true
     if (name == ":internal-testutils-truth") return true
-    if (name == ":compose:internal-lint-checks") return true
+    if (name == ":compose:lint:common") return true
+    if (name == ":compose:lint:internal-lint-checks") return true
     return false
 })
 
diff --git a/lint-checks/integration-tests/build.gradle b/lint-checks/integration-tests/build.gradle
index 3790ebf..a01e2e6 100644
--- a/lint-checks/integration-tests/build.gradle
+++ b/lint-checks/integration-tests/build.gradle
@@ -28,7 +28,7 @@
 }
 
 dependencies {
-    implementation(project(":annotation:annotation"))
+    implementation(projectOrArtifact(":annotation:annotation"))
     implementation(KOTLIN_STDLIB)
 }
 
@@ -87,9 +87,10 @@
 def lintOutputFileNormalized = project.file("${buildDir}/lint-results-normalized/lint-results-debug.xml.normalized")
 
 def normalizeLintOutput = tasks.register("normalizeLintOutput", Copy) {
+    def supportRootFolder = project.rootProject.ext.supportRootFolder
     from(lintOutputFile) {
         filter { line ->
-            return line.replace("${project.rootProject.projectDir}", "\$SUPPORT")
+            return line.replace(supportRootFolder.getAbsolutePath(), "\$SUPPORT")
         }
     }
     into(lintOutputFileNormalized.parentFile)
diff --git a/lint-checks/integration-tests/expected-lint-results.xml b/lint-checks/integration-tests/expected-lint-results.xml
index e8f2901..8333531 100644
--- a/lint-checks/integration-tests/expected-lint-results.xml
+++ b/lint-checks/integration-tests/expected-lint-results.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta04">
+<issues format="5" by="lint 4.2.0-beta06">
 
     <issue
         id="BanConcurrentHashMap"
diff --git a/lint-checks/lint-baseline.xml b/lint-checks/lint-baseline.xml
deleted file mode 100644
index 297ae16..0000000
--- a/lint-checks/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" version="4.2.0-beta02">
-
-</issues>
diff --git a/media/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java b/media/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
index 130b174..e255c74 100644
--- a/media/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/media/media/src/main/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -556,7 +556,8 @@
             // the associated intent will be handled by the component being registered
             mediaButtonIntent.setComponent(mbrComponent);
             mbrIntent = PendingIntent.getBroadcast(context,
-                    0/* requestCode, ignored */, mediaButtonIntent, 0/* flags */);
+                    0/* requestCode, ignored */, mediaButtonIntent,
+                    PendingIntent.FLAG_IMMUTABLE);
         }
 
         if (android.os.Build.VERSION.SDK_INT >= 21) {
diff --git a/media/media/src/main/java/androidx/media/session/MediaButtonReceiver.java b/media/media/src/main/java/androidx/media/session/MediaButtonReceiver.java
index c5a9a14..5ef83e3 100644
--- a/media/media/src/main/java/androidx/media/session/MediaButtonReceiver.java
+++ b/media/media/src/main/java/androidx/media/session/MediaButtonReceiver.java
@@ -269,7 +269,7 @@
         if (Build.VERSION.SDK_INT >= 16) {
             intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         }
-        return PendingIntent.getBroadcast(context, keyCode, intent, 0);
+        return PendingIntent.getBroadcast(context, keyCode, intent, PendingIntent.FLAG_IMMUTABLE);
     }
 
     /**
diff --git a/media/version-compat-tests/previous/client/src/androidTest/java/android/support/mediacompat/client/RemoteUserInfoWithMediaControllerCompatTest.java b/media/version-compat-tests/previous/client/src/androidTest/java/android/support/mediacompat/client/RemoteUserInfoWithMediaControllerCompatTest.java
index 0567306..91fb0c3 100644
--- a/media/version-compat-tests/previous/client/src/androidTest/java/android/support/mediacompat/client/RemoteUserInfoWithMediaControllerCompatTest.java
+++ b/media/version-compat-tests/previous/client/src/androidTest/java/android/support/mediacompat/client/RemoteUserInfoWithMediaControllerCompatTest.java
@@ -48,6 +48,7 @@
 import android.view.KeyEvent;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 
 import org.junit.After;
@@ -64,6 +65,7 @@
  * {@link MediaControllerCompat} methods.
  */
 @RunWith(AndroidJUnit4.class)
+@FlakyTest(bugId = 182271958)
 @LargeTest
 public class RemoteUserInfoWithMediaControllerCompatTest {
     private static final String TAG = "RemoteUserInfoCompat";
diff --git a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java b/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java
index c2e9d9a..77b03f5e 100644
--- a/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java
+++ b/media2/media2-session/src/androidTest/java/androidx/media2/session/MediaControllerTest.java
@@ -188,8 +188,6 @@
         assertEquals(seekPosition, mPlayer.mSeekPosition);
     }
 
-
-
     @Test
     public void gettersAfterConnected() throws InterruptedException {
         final int state = SessionPlayer.PLAYER_STATE_PLAYING;
@@ -199,6 +197,8 @@
         final float speed = 0.5f;
         final long timeDiff = 102;
         final MediaItem currentMediaItem = TestUtils.createMediaItemWithMetadata();
+        final int shuffleMode = SessionPlayer.SHUFFLE_MODE_ALL;
+        final int repeatMode = SessionPlayer.REPEAT_MODE_ONE;
 
         mPlayer.mLastPlayerState = state;
         mPlayer.mLastBufferingState = bufferingState;
@@ -206,6 +206,8 @@
         mPlayer.mBufferedPosition = bufferedPosition;
         mPlayer.mPlaybackSpeed = speed;
         mPlayer.mCurrentMediaItem = currentMediaItem;
+        mPlayer.mShuffleMode = shuffleMode;
+        mPlayer.mRepeatMode = repeatMode;
 
         MediaController controller = createController(mSession.getToken());
         controller.setTimeDiff(timeDiff);
@@ -214,6 +216,8 @@
         assertEquals(speed, controller.getPlaybackSpeed(), 0.0f);
         assertEquals(position + (long) (speed * timeDiff), controller.getCurrentPosition());
         assertEquals(currentMediaItem, controller.getCurrentMediaItem());
+        assertEquals(shuffleMode, controller.getShuffleMode());
+        assertEquals(repeatMode, controller.getRepeatMode());
     }
 
     @Test
@@ -222,7 +226,9 @@
         final List<MediaItem> testPlaylist = TestUtils.createMediaItems(3);
         final AudioAttributesCompat testAudioAttributes = new AudioAttributesCompat.Builder()
                 .setLegacyStreamType(AudioManager.STREAM_RING).build();
-        final CountDownLatch latch = new CountDownLatch(3);
+        final int testShuffleMode = SessionPlayer.SHUFFLE_MODE_ALL;
+        final int testRepeatMode = SessionPlayer.REPEAT_MODE_ONE;
+        final CountDownLatch latch = new CountDownLatch(5);
         mController = createController(mSession.getToken(), true, null, new ControllerCallback() {
             @Override
             public void onPlayerStateChanged(@NonNull MediaController controller, int state) {
@@ -247,12 +253,32 @@
                 assertEquals(testAudioAttributes, info.getAudioAttributes());
                 latch.countDown();
             }
+
+            @Override
+            public void onShuffleModeChanged(
+                    @NonNull MediaController controller,
+                    int shuffleMode) {
+                assertEquals(mController, controller);
+                assertEquals(testShuffleMode, shuffleMode);
+                latch.countDown();
+            }
+
+            @Override
+            public void onRepeatModeChanged(
+                    @NonNull MediaController controller,
+                    int repeatMode) {
+                assertEquals(mController, controller);
+                assertEquals(testRepeatMode, repeatMode);
+                latch.countDown();
+            }
         });
 
         MockPlayer player = new MockPlayer(0);
         player.mLastPlayerState = testState;
         player.mAudioAttributes = testAudioAttributes;
         player.mPlaylist = testPlaylist;
+        player.mShuffleMode = testShuffleMode;
+        player.mRepeatMode = testRepeatMode;
 
         mSession.updatePlayer(player);
         assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaControllerImplLegacy.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaControllerImplLegacy.java
index 965743d..e51a044 100644
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaControllerImplLegacy.java
+++ b/media2/media2-session/src/main/java/androidx/media2/session/MediaControllerImplLegacy.java
@@ -986,6 +986,7 @@
     @SuppressWarnings({"GuardedBy", "WeakerAccess"}) /* WeakerAccess for synthetic access */
     void setCurrentMediaItemLocked(MediaMetadataCompat metadata) {
         mMediaMetadataCompat = metadata;
+        int ratingType = mControllerCompat.getRatingType();
         if (metadata == null) {
             mCurrentMediaItemIndex = -1;
             mCurrentMediaItem = null;
@@ -994,7 +995,7 @@
 
         if (mQueue == null) {
             mCurrentMediaItemIndex = -1;
-            mCurrentMediaItem = MediaUtils.convertToMediaItem(metadata);
+            mCurrentMediaItem = MediaUtils.convertToMediaItem(metadata, ratingType);
             return;
         }
 
@@ -1004,7 +1005,7 @@
             for (int i = 0; i < mQueue.size(); ++i) {
                 QueueItem item = mQueue.get(i);
                 if (item.getQueueId() == queueId) {
-                    mCurrentMediaItem = MediaUtils.convertToMediaItem(metadata);
+                    mCurrentMediaItem = MediaUtils.convertToMediaItem(metadata, ratingType);
                     mCurrentMediaItemIndex = i;
                     return;
                 }
@@ -1014,7 +1015,7 @@
         String mediaId = metadata.getString(METADATA_KEY_MEDIA_ID);
         if (mediaId == null) {
             mCurrentMediaItemIndex = -1;
-            mCurrentMediaItem = MediaUtils.convertToMediaItem(metadata);
+            mCurrentMediaItem = MediaUtils.convertToMediaItem(metadata, ratingType);
             return;
         }
 
@@ -1024,7 +1025,7 @@
                 && TextUtils.equals(mediaId,
                         mQueue.get(mSkipToPlaylistIndex).getDescription().getMediaId())) {
             // metadata changed after skipToPlaylistIItem() was called.
-            mCurrentMediaItem = MediaUtils.convertToMediaItem(metadata);
+            mCurrentMediaItem = MediaUtils.convertToMediaItem(metadata, ratingType);
             mCurrentMediaItemIndex = mSkipToPlaylistIndex;
             mSkipToPlaylistIndex = -1;
             return;
@@ -1035,14 +1036,14 @@
             QueueItem item = mQueue.get(i);
             if (TextUtils.equals(mediaId, item.getDescription().getMediaId())) {
                 mCurrentMediaItemIndex = i;
-                mCurrentMediaItem = MediaUtils.convertToMediaItem(metadata);
+                mCurrentMediaItem = MediaUtils.convertToMediaItem(metadata, ratingType);
                 return;
             }
         }
 
         // Failed to find media item from the playlist.
         mCurrentMediaItemIndex = -1;
-        mCurrentMediaItem = MediaUtils.convertToMediaItem(mMediaMetadataCompat);
+        mCurrentMediaItem = MediaUtils.convertToMediaItem(mMediaMetadataCompat, ratingType);
     }
 
     private class ConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaLibraryServiceLegacyStub.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaLibraryServiceLegacyStub.java
index d72ca8d..41be4d5 100644
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaLibraryServiceLegacyStub.java
+++ b/media2/media2-session/src/main/java/androidx/media2/session/MediaLibraryServiceLegacyStub.java
@@ -33,11 +33,13 @@
 
 import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.core.util.ObjectsCompat;
 import androidx.media.MediaBrowserServiceCompat;
 import androidx.media.MediaSessionManager.RemoteUserInfo;
 import androidx.media2.common.MediaItem;
 import androidx.media2.common.MediaMetadata;
+import androidx.media2.common.SessionPlayer;
 import androidx.media2.common.SessionPlayer.PlayerResult;
 import androidx.media2.common.SessionPlayer.TrackInfo;
 import androidx.media2.common.SubtitleData;
@@ -401,6 +403,16 @@
         }
 
         @Override
+        void onPlayerChanged(int seq,
+                @Nullable SessionPlayer oldPlayer,
+                @Nullable MediaController.PlaybackInfo oldPlaybackInfo,
+                @NonNull SessionPlayer player,
+                @NonNull MediaController.PlaybackInfo playbackInfo)
+                throws RemoteException {
+            // No-op. BrowserCompat doesn't understand Controller features.
+        }
+
+        @Override
         final void setCustomLayout(int seq, @NonNull List<CommandButton> layout)
                 throws RemoteException {
             // No-op. BrowserCompat doesn't understand Controller features.
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaSession.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaSession.java
index 5ba8b5d..5572e7d 100644
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaSession.java
+++ b/media2/media2-session/src/main/java/androidx/media2/session/MediaSession.java
@@ -1182,6 +1182,9 @@
         abstract void onPlayerResult(int seq, PlayerResult result) throws RemoteException;
         abstract void onSessionResult(int seq, SessionResult result) throws RemoteException;
         abstract void onLibraryResult(int seq, LibraryResult result) throws RemoteException;
+        abstract void onPlayerChanged(int seq, @Nullable SessionPlayer oldPlayer,
+                @Nullable PlaybackInfo oldPlaybackInfo, @NonNull SessionPlayer player,
+                @NonNull PlaybackInfo playbackInfo) throws RemoteException;
 
         // Mostly matched with the methods in MediaController.ControllerCallback
         abstract void setCustomLayout(int seq, @NonNull List<CommandButton> layout)
@@ -1233,8 +1236,6 @@
     }
 
     interface MediaSessionImpl extends MediaInterface.SessionPlayer, Closeable {
-        void updatePlayer(@NonNull SessionPlayer player,
-                @Nullable SessionPlayer playlistAgent);
         void updatePlayer(@NonNull SessionPlayer player);
         @NonNull
         SessionPlayer getPlayer();
@@ -1258,7 +1259,7 @@
 
         // Internally used methods
         MediaSession getInstance();
-        MediaSessionCompat getSessionCompat();
+        @NonNull MediaSessionCompat getSessionCompat();
         void setLegacyControllerConnectionTimeoutMs(long timeoutMs);
         Context getContext();
         Executor getCallbackExecutor();
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionImplBase.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionImplBase.java
index 51caf49..869063b 100644
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionImplBase.java
+++ b/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionImplBase.java
@@ -52,7 +52,6 @@
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.MediaSessionCompat.Token;
 import android.support.v4.media.session.PlaybackStateCompat;
-import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.Surface;
@@ -88,9 +87,6 @@
 import java.util.concurrent.atomic.AtomicInteger;
 
 class MediaSessionImplBase implements MediaSession.MediaSessionImpl {
-    private static final String DEFAULT_MEDIA_SESSION_TAG_PREFIX = "androidx.media2.session.id";
-    private static final String DEFAULT_MEDIA_SESSION_TAG_DELIM = ".";
-
     // Create a static lock for synchronize methods below.
     // We'd better not use MediaSessionImplBase.class for synchronized(), which indirectly exposes
     // lock object to the outside of the class.
@@ -116,7 +112,6 @@
     private final Context mContext;
     private final HandlerThread mHandlerThread;
     private final Handler mHandler;
-    private final MediaSessionCompat mSessionCompat;
     private final MediaSessionStub mSessionStub;
     private final MediaSessionLegacyStub mSessionLegacyStub;
     private final String mSessionId;
@@ -137,11 +132,6 @@
 
     @GuardedBy("mLock")
     @SuppressWarnings("WeakerAccess") /* synthetic access */
-    @Nullable
-    VolumeProviderCompat mVolumeProviderCompat;
-
-    @GuardedBy("mLock")
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
     SessionPlayer mPlayer;
 
     @GuardedBy("mLock")
@@ -179,8 +169,6 @@
                 .appendPath(String.valueOf(SystemClock.elapsedRealtime())).build();
         mSessionToken = new SessionToken(new SessionTokenImplBase(Process.myUid(),
                 SessionToken.TYPE_SESSION, context.getPackageName(), mSessionStub, tokenExtras));
-        String sessionCompatId = TextUtils.join(DEFAULT_MEDIA_SESSION_TAG_DELIM,
-                new String[] {DEFAULT_MEDIA_SESSION_TAG_PREFIX, id});
 
         ComponentName mbrComponent = null;
         synchronized (STATIC_LOCK) {
@@ -226,83 +214,40 @@
             mBroadcastReceiver = null;
         }
 
-        mSessionCompat = new MediaSessionCompat(context, sessionCompatId, mbrComponent,
-                mMediaButtonIntent, mSessionToken.getExtras(), mSessionToken);
-        // NOTE: mSessionLegacyStub should be created after mSessionCompat created.
-        mSessionLegacyStub = new MediaSessionLegacyStub(this, mHandler);
-
-        mSessionCompat.setSessionActivity(sessionActivity);
-        mSessionCompat.setFlags(MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS);
+        mSessionLegacyStub = new MediaSessionLegacyStub(this, mbrComponent,
+                mMediaButtonIntent, mHandler);
 
         updatePlayer(player);
+
         // Do followings at the last moment. Otherwise commands through framework would be sent to
         // this session while initializing, and end up with unexpected situation.
-        mSessionCompat.setCallback(mSessionLegacyStub, mHandler);
-        mSessionCompat.setActive(true);
-    }
-
-    @Override
-    public void updatePlayer(@NonNull SessionPlayer player,
-            @Nullable SessionPlayer playlistAgent) {
-        // No-op
+        mSessionLegacyStub.start();
     }
 
     // TODO(jaewan): Remove SuppressLint when removing duplication session callback.
     @Override
     @SuppressLint("WrongConstant")
     public void updatePlayer(@NonNull SessionPlayer player) {
-        final boolean isPlaybackInfoChanged;
-
         final SessionPlayer oldPlayer;
-        final MediaController.PlaybackInfo info = createPlaybackInfo(player, null);
-
-        VolumeProviderCompat volumeProviderCompat = player instanceof RemoteSessionPlayer
-                ? createVolumeProviderCompat((RemoteSessionPlayer) player) : null;
+        final MediaController.PlaybackInfo oldPlaybackInfo;
+        final MediaController.PlaybackInfo playbackInfo = createPlaybackInfo(player, null);
 
         synchronized (mLock) {
-            isPlaybackInfoChanged = !info.equals(mPlaybackInfo);
-
+            if (mPlayer == player) {
+                return;
+            }
             oldPlayer = mPlayer;
             mPlayer = player;
-            mPlaybackInfo = info;
-            mVolumeProviderCompat = volumeProviderCompat;
+            oldPlaybackInfo = mPlaybackInfo;
+            mPlaybackInfo = playbackInfo;
         }
 
-        if (oldPlayer != player) {
-            if (oldPlayer != null) {
-                oldPlayer.unregisterPlayerCallback(mPlayerCallback);
-            }
-            player.registerPlayerCallback(mCallbackExecutor, mPlayerCallback);
+        if (oldPlayer != null) {
+            oldPlayer.unregisterPlayerCallback(mPlayerCallback);
         }
+        player.registerPlayerCallback(mCallbackExecutor, mPlayerCallback);
 
-        if (oldPlayer == null) {
-            // updatePlayerConnector() is called inside of the constructor.
-            // There's no connected controllers at this moment, so just initialize session compat's
-            // playback state. Otherwise, framework doesn't know whether this is ready to receive
-            // media key event.
-            mSessionCompat.setPlaybackState(createPlaybackStateCompat());
-        } else {
-            if (player != oldPlayer) {
-                final int state = getPlayerState();
-                mCallbackExecutor.execute(new Runnable() {
-                    @Override
-                    public void run() {
-                        mCallback.onPlayerStateChanged(getInstance(), state);
-                    }
-                });
-                notifyPlayerUpdatedNotLocked(oldPlayer);
-            }
-            if (isPlaybackInfoChanged) {
-                notifyPlaybackInfoChangedNotLocked(info);
-            }
-        }
-
-        if (player instanceof RemoteSessionPlayer) {
-            mSessionCompat.setPlaybackToRemote(volumeProviderCompat);
-        } else {
-            int stream = getLegacyStreamType(player.getAudioAttributes());
-            mSessionCompat.setPlaybackToLocal(stream);
-        }
+        notifyPlayerUpdatedNotLocked(oldPlayer, oldPlaybackInfo, player, playbackInfo);
     }
 
     @NonNull
@@ -312,7 +257,7 @@
                 player.getAudioAttributes();
 
         if (!(player instanceof RemoteSessionPlayer)) {
-            int stream = getLegacyStreamType(attrs);
+            int stream = MediaUtils.getLegacyStreamType(attrs);
             int controlType = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
             if (Build.VERSION.SDK_INT >= 21 && mAudioManager.isVolumeFixed()) {
                 controlType = VolumeProviderCompat.VOLUME_CONTROL_FIXED;
@@ -334,24 +279,6 @@
         }
     }
 
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    static int getLegacyStreamType(@Nullable AudioAttributesCompat attrs) {
-        int stream;
-        if (attrs == null) {
-            stream = AudioManager.STREAM_MUSIC;
-        } else {
-            stream = attrs.getLegacyStreamType();
-            if (stream == AudioManager.USE_DEFAULT_STREAM_TYPE) {
-                // Usually, AudioAttributesCompat#getLegacyStreamType() does not return
-                // USE_DEFAULT_STREAM_TYPE unless the developer sets it with
-                // AudioAttributesCompat.Builder#setLegacyStreamType().
-                // But for safety, let's convert USE_DEFAULT_STREAM_TYPE to STREAM_MUSIC here.
-                stream = AudioManager.STREAM_MUSIC;
-            }
-        }
-        return stream;
-    }
-
     @Override
     public void close() {
         SessionPlayer player;
@@ -368,8 +295,8 @@
             player = mPlayer;
         }
         player.unregisterPlayerCallback(mPlayerCallback);
-        mSessionCompat.release();
         mMediaButtonIntent.cancel();
+        mSessionLegacyStub.close();
         if (mBroadcastReceiver != null) {
             mContext.unregisterReceiver(mBroadcastReceiver);
         }
@@ -971,8 +898,9 @@
     }
 
     @Override
+    @NonNull
     public MediaSessionCompat getSessionCompat() {
-        return mSessionCompat;
+        return mSessionLegacyStub.getSessionCompat();
     }
 
     @Override
@@ -1053,7 +981,7 @@
         synchronized (mLock) {
             if (mBrowserServiceLegacyStub == null) {
                 mBrowserServiceLegacyStub = createLegacyBrowserServiceLocked(mContext,
-                        mSessionToken, mSessionCompat.getSessionToken());
+                        mSessionToken, mSessionLegacyStub.getSessionCompat().getSessionToken());
             }
             legacyStub = mBrowserServiceLegacyStub;
         }
@@ -1074,22 +1002,6 @@
                 && player.getPlayerState() != SessionPlayer.PLAYER_STATE_ERROR;
     }
 
-    private @Nullable MediaItem getCurrentMediaItemOrNull() {
-        final SessionPlayer player;
-        synchronized (mLock) {
-            player = mPlayer;
-        }
-        return player != null ? player.getCurrentMediaItem() : null;
-    }
-
-    private @Nullable List<MediaItem> getPlaylistOrNull() {
-        final SessionPlayer player;
-        synchronized (mLock) {
-            player = mPlayer;
-        }
-        return player != null ? player.getPlaylist() : null;
-    }
-
     private ListenableFuture<PlayerResult> dispatchPlayerTask(
             @NonNull PlayerTask<ListenableFuture<PlayerResult>> command) {
         ResolvableFuture<PlayerResult> result = ResolvableFuture.create();
@@ -1118,98 +1030,15 @@
 
     // TODO(jaewan): Remove SuppressLint when removing duplication session callback.
     @SuppressLint("WrongConstant")
-    private void notifyPlayerUpdatedNotLocked(SessionPlayer oldPlayer) {
-        // Tells the playlist change first, to current item can change be notified with an item
-        // within the playlist.
-        List<MediaItem> oldPlaylist = oldPlayer.getPlaylist();
-        final List<MediaItem> newPlaylist = getPlaylistOrNull();
-        if (!ObjectsCompat.equals(oldPlaylist, newPlaylist)) {
-            dispatchRemoteControllerTaskWithoutReturn(new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onPlaylistChanged(seq,
-                            newPlaylist, getPlaylistMetadata(), getCurrentMediaItemIndex(),
-                            getPreviousMediaItemIndex(), getNextMediaItemIndex());
-                }
-            });
-        } else {
-            MediaMetadata oldMetadata = oldPlayer.getPlaylistMetadata();
-            final MediaMetadata newMetadata = getPlaylistMetadata();
-            if (!ObjectsCompat.equals(oldMetadata, newMetadata)) {
-                dispatchRemoteControllerTaskWithoutReturn(new RemoteControllerTask() {
-                    @Override
-                    public void run(ControllerCb callback, int seq) throws RemoteException {
-                        callback.onPlaylistMetadataChanged(seq, newMetadata);
-                    }
-                });
-            }
-        }
-        MediaItem oldCurrentItem = oldPlayer.getCurrentMediaItem();
-        final MediaItem newCurrentItem = getCurrentMediaItemOrNull();
-        if (!ObjectsCompat.equals(oldCurrentItem, newCurrentItem)) {
-            dispatchRemoteControllerTaskWithoutReturn(new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onCurrentMediaItemChanged(seq, newCurrentItem,
-                            getCurrentMediaItemIndex(), getPreviousMediaItemIndex(),
-                            getNextMediaItemIndex());
-                }
-            });
-        }
-        final @SessionPlayer.RepeatMode int repeatMode = getRepeatMode();
-        if (oldPlayer.getRepeatMode() != repeatMode) {
-            dispatchRemoteControllerTaskWithoutReturn(new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onRepeatModeChanged(seq, repeatMode, getCurrentMediaItemIndex(),
-                            getPreviousMediaItemIndex(), getNextMediaItemIndex());
-                }
-            });
-        }
-        final @SessionPlayer.ShuffleMode int shuffleMode = getShuffleMode();
-        if (oldPlayer.getShuffleMode() != shuffleMode) {
-            dispatchRemoteControllerTaskWithoutReturn(new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onShuffleModeChanged(seq, shuffleMode, getCurrentMediaItemIndex(),
-                            getPreviousMediaItemIndex(), getNextMediaItemIndex());
-                }
-            });
-        }
-
-        // Always forcefully send the player state and buffered state to send the current position
-        // and buffered position.
-        final long currentTimeMs = SystemClock.elapsedRealtime();
-        final long positionMs = getCurrentPosition();
-        final int playerState = getPlayerState();
+    private void notifyPlayerUpdatedNotLocked(@Nullable SessionPlayer oldPlayer,
+            @Nullable MediaController.PlaybackInfo oldPlaybackInfo,
+            @NonNull SessionPlayer player, @NonNull MediaController.PlaybackInfo playbackInfo) {
         dispatchRemoteControllerTaskWithoutReturn(new RemoteControllerTask() {
             @Override
             public void run(ControllerCb callback, int seq) throws RemoteException {
-                callback.onPlayerStateChanged(seq, currentTimeMs, positionMs, playerState);
+                callback.onPlayerChanged(seq, oldPlayer, oldPlaybackInfo, player, playbackInfo);
             }
         });
-        final MediaItem item = getCurrentMediaItemOrNull();
-        if (item != null) {
-            final int bufferingState = getBufferingState();
-            final long bufferedPositionMs = getBufferedPosition();
-            dispatchRemoteControllerTaskWithoutReturn(new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onBufferingStateChanged(seq, item, bufferingState, bufferedPositionMs,
-                            SystemClock.elapsedRealtime(), getCurrentPosition());
-                }
-            });
-        }
-        final float speed = getPlaybackSpeed();
-        if (speed != oldPlayer.getPlaybackSpeed()) {
-            dispatchRemoteControllerTaskWithoutReturn(new RemoteControllerTask() {
-                @Override
-                public void run(ControllerCb callback, int seq) throws RemoteException {
-                    callback.onPlaybackSpeedChanged(seq, currentTimeMs, positionMs, speed);
-                }
-            });
-        }
-        // Note: AudioInfo is updated outside of this API.
     }
 
     @SuppressWarnings("WeakerAccess") /* synthetic access */
@@ -1330,26 +1159,6 @@
         return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
     }
 
-    private static VolumeProviderCompat createVolumeProviderCompat(
-            @NonNull RemoteSessionPlayer player) {
-        return new VolumeProviderCompat(player.getVolumeControlType(), player.getMaxVolume(),
-                player.getVolume()) {
-            // TODO(b/138091975) Do not ignore the returned Future.
-            @SuppressWarnings("FutureReturnValueIgnored")
-            @Override
-            public void onSetVolumeTo(int volume) {
-                player.setVolume(volume);
-            }
-
-            // TODO(b/138091975) Do not ignore the returned Future.
-            @SuppressWarnings("FutureReturnValueIgnored")
-            @Override
-            public void onAdjustVolume(int direction) {
-                player.adjustVolume(direction);
-            }
-        };
-    }
-
     ///////////////////////////////////////////////////
     // Inner classes
     ///////////////////////////////////////////////////
@@ -1549,14 +1358,6 @@
             }
             if (!ObjectsCompat.equals(newInfo, oldInfo)) {
                 session.notifyPlaybackInfoChangedNotLocked(newInfo);
-                if (!(player instanceof RemoteSessionPlayer)) {
-                    int oldStreamType = getLegacyStreamType(
-                            oldInfo == null ? null : oldInfo.getAudioAttributes());
-                    int newStreamType = getLegacyStreamType(newInfo.getAudioAttributes());
-                    if (oldStreamType != newStreamType) {
-                        session.getSessionCompat().setPlaybackToLocal(newStreamType);
-                    }
-                }
             }
         }
 
@@ -1650,21 +1451,16 @@
             MediaController.PlaybackInfo newInfo =
                     session.createPlaybackInfo(player, /* audioAttributes= */ null);
             MediaController.PlaybackInfo oldInfo;
-            VolumeProviderCompat volumeProviderCompat;
             synchronized (session.mLock) {
                 if (session.mPlayer != player) {
                     return;
                 }
                 oldInfo = session.mPlaybackInfo;
                 session.mPlaybackInfo = newInfo;
-                volumeProviderCompat = session.mVolumeProviderCompat;
             }
             if (!ObjectsCompat.equals(newInfo, oldInfo)) {
                 session.notifyPlaybackInfoChangedNotLocked(newInfo);
             }
-            if (volumeProviderCompat != null) {
-                volumeProviderCompat.setCurrentVolume(volume);
-            }
         }
 
         private MediaSessionImplBase getSession() {
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionLegacyStub.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionLegacyStub.java
index c232360..0ebda7a 100644
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionLegacyStub.java
+++ b/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionLegacyStub.java
@@ -22,6 +22,8 @@
 import static androidx.media2.session.SessionCommand.COMMAND_VERSION_CURRENT;
 import static androidx.media2.session.SessionResult.RESULT_SUCCESS;
 
+import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Context;
 import android.net.Uri;
 import android.os.Build;
@@ -45,8 +47,11 @@
 import androidx.core.util.ObjectsCompat;
 import androidx.media.MediaSessionManager;
 import androidx.media.MediaSessionManager.RemoteUserInfo;
+import androidx.media.VolumeProviderCompat;
 import androidx.media2.common.MediaItem;
 import androidx.media2.common.MediaMetadata;
+import androidx.media2.common.Rating;
+import androidx.media2.common.SessionPlayer;
 import androidx.media2.common.SessionPlayer.PlayerResult;
 import androidx.media2.common.SessionPlayer.TrackInfo;
 import androidx.media2.common.SubtitleData;
@@ -58,13 +63,17 @@
 import androidx.media2.session.MediaSession.ControllerInfo;
 import androidx.media2.session.SessionCommand.CommandCode;
 
+import java.io.Closeable;
 import java.util.List;
 import java.util.Set;
 
 // Getting the commands from MediaControllerCompat'
-class MediaSessionLegacyStub extends MediaSessionCompat.Callback {
+class MediaSessionLegacyStub extends MediaSessionCompat.Callback implements Closeable {
 
     private static final String TAG = "MediaSessionLegacyStub";
+    private static final String DEFAULT_MEDIA_SESSION_TAG_PREFIX = "androidx.media2.session.id";
+    private static final String DEFAULT_MEDIA_SESSION_TAG_DELIM = ".";
+
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     // Used to call onDisconnected() after the timeout.
@@ -92,11 +101,15 @@
     final Context mContext;
     final ControllerCb mControllerLegacyCbForBroadcast;
     final ConnectionTimeoutHandler mConnectionTimeoutHandler;
+    final MediaSessionCompat mSessionCompat;
 
     @SuppressWarnings("WeakerAccess") /* synthetic access */
     volatile long mConnectionTimeoutMs;
 
-    MediaSessionLegacyStub(MediaSession.MediaSessionImpl session, Handler handler) {
+    private final Handler mHandler;
+
+    MediaSessionLegacyStub(MediaSession.MediaSessionImpl session,
+            ComponentName mbrComponent, PendingIntent mediaButtonIntent, Handler handler) {
         mSessionImpl = session;
         mContext = mSessionImpl.getContext();
         mSessionManager = MediaSessionManager.getSessionManager(mContext);
@@ -104,6 +117,37 @@
         mConnectionTimeoutHandler = new ConnectionTimeoutHandler(handler.getLooper());
         mConnectedControllersManager = new ConnectedControllersManager<>(session);
         mConnectionTimeoutMs = DEFAULT_CONNECTION_TIMEOUT_MS;
+        mHandler = handler;
+
+        String sessionCompatId = TextUtils.join(DEFAULT_MEDIA_SESSION_TAG_DELIM,
+                new String[] {DEFAULT_MEDIA_SESSION_TAG_PREFIX, session.getId()});
+        mSessionCompat = new MediaSessionCompat(mContext,
+                sessionCompatId,
+                mbrComponent,
+                mediaButtonIntent, session.getToken().getExtras(),
+                session.getToken());
+        mSessionCompat.setSessionActivity(session.getSessionActivity());
+        mSessionCompat.setFlags(MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS);
+        // Note: Rest of mSessionCompat initialization will be done via
+        // {@link ControllerLegacyCbForBroadcast#onPlayerChanged} and {@link #start}, called
+        // indirectly by {@link MediaSessionImplBase#MediaSessionImplBase}.
+    }
+
+    /**
+     * Starts to receive and send commands.
+     */
+    public void start() {
+        mSessionCompat.setCallback(this, mHandler);
+        mSessionCompat.setActive(true);
+    }
+
+    @Override
+    public void close() {
+        mSessionCompat.release();
+    }
+
+    public MediaSessionCompat getSessionCompat() {
+        return mSessionCompat;
     }
 
     @Override
@@ -508,7 +552,7 @@
             return;
         }
         final RemoteUserInfo remoteUserInfo =
-                mSessionImpl.getSessionCompat().getCurrentControllerInfo();
+                mSessionCompat.getCurrentControllerInfo();
         if (remoteUserInfo == null) {
             Log.d(TAG, "RemoteUserInfo is null, ignoring command=" + sessionCommand
                     + ", commandCode=" + commandCode);
@@ -596,6 +640,50 @@
         mConnectionTimeoutMs = timeoutMs;
     }
 
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    static VolumeProviderCompat createVolumeProviderCompat(
+            @NonNull RemoteSessionPlayer player) {
+        return new VolumeProviderCompat(player.getVolumeControlType(), player.getMaxVolume(),
+                player.getVolume()) {
+            // TODO(b/138091975) Do not ignore the returned Future.
+            @SuppressWarnings("FutureReturnValueIgnored")
+            @Override
+            public void onSetVolumeTo(int volume) {
+                player.setVolume(volume);
+            }
+
+            // TODO(b/138091975) Do not ignore the returned Future.
+            @SuppressWarnings("FutureReturnValueIgnored")
+            @Override
+            public void onAdjustVolume(int direction) {
+                player.adjustVolume(direction);
+            }
+        };
+    }
+
+    @SuppressWarnings("WeakerAccess") /* synthetic access */
+    static int getRatingType(@Nullable Rating rating) {
+        if (rating instanceof HeartRating) {
+            return RatingCompat.RATING_HEART;
+        } else if (rating instanceof ThumbRating) {
+            return RatingCompat.RATING_THUMB_UP_DOWN;
+        } else if (rating instanceof StarRating) {
+            switch (((StarRating) rating).getMaxStars()) {
+                case 3:
+                    return RatingCompat.RATING_3_STARS;
+                case 4:
+                    return RatingCompat.RATING_4_STARS;
+                case 5:
+                    return RatingCompat.RATING_5_STARS;
+                default:
+                    return RatingCompat.RATING_NONE;
+            }
+        } else if (rating instanceof PercentageRating) {
+            return RatingCompat.RATING_PERCENTAGE;
+        }
+        return RatingCompat.RATING_NONE;
+    }
+
     @FunctionalInterface
     private interface SessionTask {
         void run(ControllerInfo controller) throws RemoteException;
@@ -625,6 +713,13 @@
         }
 
         @Override
+        void onPlayerChanged(int seq, @Nullable SessionPlayer oldPlayer,
+                @Nullable PlaybackInfo oldPlaybackInfo, @NonNull SessionPlayer player,
+                @NonNull PlaybackInfo playbackInfo) throws RemoteException {
+            // no-op
+        }
+
+        @Override
         void setCustomLayout(int seq, @NonNull List<CommandButton> layout) throws RemoteException {
             // no-op.
         }
@@ -788,14 +883,70 @@
         }
 
         @Override
+        void onPlayerChanged(int seq, @Nullable SessionPlayer oldPlayer,
+                @Nullable PlaybackInfo oldPlaybackInfo,
+                @NonNull SessionPlayer player, @NonNull PlaybackInfo playbackInfo)
+                throws RemoteException {
+            // Tells the playlist change first, so current media item index change notification
+            // can point to the valid current media item in the playlist.
+            if (oldPlayer == null
+                    || !ObjectsCompat.equals(oldPlayer.getPlaylist(), player.getPlaylist())) {
+                onPlaylistChanged(seq,
+                        player.getPlaylist(), player.getPlaylistMetadata(),
+                        player.getCurrentMediaItemIndex(),
+                        player.getPreviousMediaItemIndex(),
+                        player.getNextMediaItemIndex());
+            } else if (oldPlayer == null
+                    || !ObjectsCompat.equals(oldPlayer.getPlaylistMetadata(),
+                            player.getPlaylistMetadata())) {
+                onPlaylistMetadataChanged(seq, player.getPlaylistMetadata());
+            }
+            if (oldPlayer == null || oldPlayer.getShuffleMode() != player.getShuffleMode()) {
+                onShuffleModeChanged(seq, player.getShuffleMode(),
+                        player.getCurrentMediaItemIndex(), player.getPreviousMediaItemIndex(),
+                        player.getNextMediaItemIndex());
+            }
+            if (oldPlayer == null || oldPlayer.getRepeatMode() != player.getRepeatMode()) {
+                onRepeatModeChanged(seq, player.getRepeatMode(),
+                        player.getCurrentMediaItemIndex(), player.getPreviousMediaItemIndex(),
+                        player.getNextMediaItemIndex());
+            }
+            if (oldPlayer == null
+                    || !ObjectsCompat.equals(oldPlayer.getCurrentMediaItem(),
+                            player.getCurrentMediaItem())) {
+                // Note: This will update PlaybackStateCompat.
+                onCurrentMediaItemChanged(seq, player.getCurrentMediaItem(),
+                        player.getCurrentMediaItemIndex(), player.getPreviousMediaItemIndex(),
+                        player.getNextMediaItemIndex());
+            } else {
+                // If PlaybackStateCompat isn't updated by above if-statement, forcefully update
+                // PlaybackStateCompat to tell the latest position and its event
+                // time. This would also update playback speed and buffering/player state.
+                mSessionCompat.setPlaybackState(mSessionImpl.createPlaybackStateCompat());
+            }
+
+            // Forcefully update playback info to update VolumeProviderCompat attached to the
+            // old player.
+            onPlaybackInfoChanged(seq, playbackInfo);
+        }
+
+        @Override
         void setCustomLayout(int seq, @NonNull List<CommandButton> layout) throws RemoteException {
             throw new AssertionError("This shouldn't be called");
         }
 
         @Override
-        void onPlaybackInfoChanged(int seq, @NonNull PlaybackInfo info) throws RemoteException {
-            // no-op. Calling MediaSessionCompat#setPlaybackToLocal/Remote
-            // is already done in updatePlayerConnector().
+        void onPlaybackInfoChanged(int seq,
+                @NonNull PlaybackInfo playbackInfo) throws RemoteException {
+            if (playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
+                VolumeProviderCompat volumeProviderCompat =
+                        createVolumeProviderCompat(
+                                (RemoteSessionPlayer) mSessionImpl.getPlayer());
+                mSessionCompat.setPlaybackToRemote(volumeProviderCompat);
+            } else {
+                int stream = MediaUtils.getLegacyStreamType(playbackInfo.getAudioAttributes());
+                mSessionCompat.setPlaybackToLocal(stream);
+            }
         }
 
         @Override
@@ -814,7 +965,7 @@
         void onPlayerStateChanged(int seq, long eventTimeMs, long positionMs, int playerState)
                 throws RemoteException {
             // Note: This method does not use any of the given arguments.
-            mSessionImpl.getSessionCompat().setPlaybackState(
+            mSessionCompat.setPlaybackState(
                     mSessionImpl.createPlaybackStateCompat());
         }
 
@@ -822,7 +973,7 @@
         void onPlaybackSpeedChanged(int seq, long eventTimeMs, long positionMs, float speed)
                 throws RemoteException {
             // Note: This method does not use any of the given arguments.
-            mSessionImpl.getSessionCompat().setPlaybackState(
+            mSessionCompat.setPlaybackState(
                     mSessionImpl.createPlaybackStateCompat());
         }
 
@@ -830,7 +981,7 @@
         void onBufferingStateChanged(int seq, @NonNull MediaItem item, int bufferingState,
                 long bufferedPositionMs, long eventTimeMs, long positionMs) throws RemoteException {
             // Note: This method does not use any of the given arguments.
-            mSessionImpl.getSessionCompat().setPlaybackState(
+            mSessionCompat.setPlaybackState(
                     mSessionImpl.createPlaybackStateCompat());
         }
 
@@ -838,16 +989,21 @@
         void onSeekCompleted(int seq, long eventTimeMs, long positionMs, long position)
                 throws RemoteException {
             // Note: This method does not use any of the given arguments.
-            mSessionImpl.getSessionCompat().setPlaybackState(
+            mSessionCompat.setPlaybackState(
                     mSessionImpl.createPlaybackStateCompat());
         }
 
         @Override
         void onCurrentMediaItemChanged(int seq, MediaItem item, int currentIdx, int previousIdx,
                 int nextIdx) throws RemoteException {
-            mSessionImpl.getSessionCompat().setMetadata(item == null ? null
-                    : MediaUtils.convertToMediaMetadataCompat(item.getMetadata()));
-            mSessionImpl.getSessionCompat().setPlaybackState(
+            MediaMetadata metadata = (item == null) ? null : item.getMetadata();
+            mSessionCompat.setMetadata(MediaUtils.convertToMediaMetadataCompat(metadata));
+            int ratingType = getRatingType(
+                    (metadata == null)
+                            ? null
+                            : metadata.getRating(MediaMetadata.METADATA_KEY_USER_RATING));
+            mSessionCompat.setRatingType(ratingType);
+            mSessionCompat.setPlaybackState(
                     mSessionImpl.createPlaybackStateCompat());
         }
 
@@ -856,7 +1012,7 @@
                 int currentIdx, int previousIdx, int nextIdx) throws RemoteException {
             if (Build.VERSION.SDK_INT < 21) {
                 if (playlist == null) {
-                    mSessionImpl.getSessionCompat().setQueue(null);
+                    mSessionCompat.setQueue(null);
                 } else {
                     // In order to avoid TransactionTooLargeException for below API 21, we need to
                     // cut the list so that it doesn't exceed the binder transaction limit.
@@ -867,13 +1023,13 @@
                         Log.i(TAG, "Sending " + truncatedList.size() + " items out of "
                                 + playlist.size());
                     }
-                    mSessionImpl.getSessionCompat().setQueue(truncatedList);
+                    mSessionCompat.setQueue(truncatedList);
                 }
 
             } else {
                 // Framework MediaSession#setQueue() uses ParceledListSlice,
                 // which means we can safely send long lists.
-                mSessionImpl.getSessionCompat().setQueue(
+                mSessionCompat.setQueue(
                         MediaUtils.convertToQueueItemList(playlist));
             }
             onPlaylistMetadataChanged(seq, metadata);
@@ -882,7 +1038,7 @@
         @Override
         void onPlaylistMetadataChanged(int seq, MediaMetadata metadata) throws RemoteException {
             // Since there is no 'queue metadata', only set title of the queue.
-            CharSequence oldTitle = mSessionImpl.getSessionCompat().getController().getQueueTitle();
+            CharSequence oldTitle = mSessionCompat.getController().getQueueTitle();
             CharSequence newTitle = null;
 
             if (metadata != null) {
@@ -893,20 +1049,20 @@
             }
 
             if (!TextUtils.equals(oldTitle, newTitle)) {
-                mSessionImpl.getSessionCompat().setQueueTitle(newTitle);
+                mSessionCompat.setQueueTitle(newTitle);
             }
         }
 
         @Override
         void onShuffleModeChanged(int seq, int shuffleMode, int currentIdx, int previousIdx,
                 int nextIdx) throws RemoteException {
-            mSessionImpl.getSessionCompat().setShuffleMode(shuffleMode);
+            mSessionCompat.setShuffleMode(shuffleMode);
         }
 
         @Override
         void onRepeatModeChanged(int seq, int repeatMode, int currentIdx, int previousIdx,
                 int nextIdx) throws RemoteException {
-            mSessionImpl.getSessionCompat().setRepeatMode(repeatMode);
+            mSessionCompat.setRepeatMode(repeatMode);
         }
 
         @Override
@@ -918,7 +1074,7 @@
                                 state.getPlaybackSpeed())
                         .build();
             }
-            mSessionImpl.getSessionCompat().setPlaybackState(state);
+            mSessionCompat.setPlaybackState(state);
         }
 
         @Override
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionStub.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionStub.java
index 0c28e40..719af29 100644
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionStub.java
+++ b/media2/media2-session/src/main/java/androidx/media2/session/MediaSessionStub.java
@@ -29,7 +29,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.support.v4.media.session.MediaSessionCompat;
+import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
@@ -43,6 +43,7 @@
 import androidx.media2.common.MediaMetadata;
 import androidx.media2.common.MediaParcelUtils;
 import androidx.media2.common.Rating;
+import androidx.media2.common.SessionPlayer;
 import androidx.media2.common.SessionPlayer.PlayerResult;
 import androidx.media2.common.SessionPlayer.TrackInfo;
 import androidx.media2.common.SubtitleData;
@@ -490,10 +491,8 @@
                 new SessionCallbackTask<Integer>() {
                     @Override
                     public Integer run(MediaSessionImpl sessionImpl, ControllerInfo controller) {
-                        MediaSessionCompat sessionCompat = sessionImpl.getSessionCompat();
-                        if (sessionCompat != null) {
-                            sessionCompat.getController().setVolumeTo(value, flags);
-                        }
+                        sessionImpl.getSessionCompat()
+                                .getController().setVolumeTo(value, flags);
                         return SessionResult.RESULT_SUCCESS;
                     }
                 });
@@ -509,10 +508,8 @@
                 new SessionCallbackTask<Integer>() {
                     @Override
                     public Integer run(MediaSessionImpl sessionImpl, ControllerInfo controller) {
-                        MediaSessionCompat sessionCompat = sessionImpl.getSessionCompat();
-                        if (sessionCompat != null) {
-                            sessionCompat.getController().adjustVolume(direction, flags);
-                        }
+                        sessionImpl.getSessionCompat()
+                                .getController().adjustVolume(direction, flags);
                         return SessionResult.RESULT_SUCCESS;
                     }
                 });
@@ -1238,6 +1235,78 @@
         }
 
         @Override
+        void onPlayerChanged(int seq, @Nullable SessionPlayer oldPlayer,
+                @Nullable PlaybackInfo oldPlaybackInfo,
+                @NonNull SessionPlayer player, @NonNull PlaybackInfo playbackInfo)
+                throws RemoteException {
+            if (oldPlayer == null) {
+                // Ignore player changed callback called in initialization.
+                return;
+            }
+            MediaSessionImpl sessionImpl = mSessionImpl.get();
+            if (sessionImpl == null) {
+                return;
+            }
+            // Tells the playlist change first, so current media item index change notification
+            // can point to the valid current media item in the playlist.
+            List<MediaItem> oldPlaylist = oldPlayer.getPlaylist();
+            final List<MediaItem> newPlaylist = player.getPlaylist();
+            if (!ObjectsCompat.equals(oldPlaylist, newPlaylist)) {
+                onPlaylistChanged(seq,
+                        newPlaylist, player.getPlaylistMetadata(),
+                        player.getCurrentMediaItemIndex(),
+                        player.getPreviousMediaItemIndex(),
+                        player.getNextMediaItemIndex());
+            } else {
+                MediaMetadata oldMetadata = oldPlayer.getPlaylistMetadata();
+                final MediaMetadata newMetadata = player.getPlaylistMetadata();
+                if (!ObjectsCompat.equals(oldMetadata, newMetadata)) {
+                    onPlaylistMetadataChanged(seq, newMetadata);
+                }
+            }
+            MediaItem oldCurrentItem = oldPlayer.getCurrentMediaItem();
+            final MediaItem newCurrentItem = player.getCurrentMediaItem();
+            if (!ObjectsCompat.equals(oldCurrentItem, newCurrentItem)) {
+                onCurrentMediaItemChanged(seq, newCurrentItem,
+                        player.getCurrentMediaItemIndex(), player.getPreviousMediaItemIndex(),
+                        player.getNextMediaItemIndex());
+            }
+            final @SessionPlayer.RepeatMode int repeatMode = player.getRepeatMode();
+            if (oldPlayer.getRepeatMode() != repeatMode) {
+                onRepeatModeChanged(seq, repeatMode, player.getCurrentMediaItemIndex(),
+                        player.getPreviousMediaItemIndex(), player.getNextMediaItemIndex());
+            }
+            final @SessionPlayer.ShuffleMode int shuffleMode = player.getShuffleMode();
+            if (oldPlayer.getShuffleMode() != shuffleMode) {
+                onShuffleModeChanged(seq, shuffleMode, player.getCurrentMediaItemIndex(),
+                        player.getPreviousMediaItemIndex(), player.getNextMediaItemIndex());
+            }
+
+            // Always forcefully send the player state and buffered state to send the current
+            // position and buffered position.
+            final long currentTimeMs = SystemClock.elapsedRealtime();
+            final long positionMs = player.getCurrentPosition();
+            final int playerState = player.getPlayerState();
+            onPlayerStateChanged(seq, currentTimeMs, positionMs, playerState);
+
+            final MediaItem item = player.getCurrentMediaItem();
+            if (item != null) {
+                final int bufferingState = player.getBufferingState();
+                final long bufferedPositionMs = player.getBufferedPosition();
+                onBufferingStateChanged(seq, item, bufferingState, bufferedPositionMs,
+                        SystemClock.elapsedRealtime(), player.getCurrentPosition());
+            }
+            final float speed = player.getPlaybackSpeed();
+            if (speed != oldPlayer.getPlaybackSpeed()) {
+                onPlaybackSpeedChanged(seq, currentTimeMs, positionMs, speed);
+            }
+
+            if (!ObjectsCompat.equals(oldPlaybackInfo, playbackInfo)) {
+                onPlaybackInfoChanged(seq, playbackInfo);
+            }
+        }
+
+        @Override
         void setCustomLayout(int seq, @NonNull List<CommandButton> layout) throws RemoteException {
             mIControllerCallback.onSetCustomLayout(seq,
                     MediaUtils.convertCommandButtonListToParcelImplList(layout));
diff --git a/media2/media2-session/src/main/java/androidx/media2/session/MediaUtils.java b/media2/media2-session/src/main/java/androidx/media2/session/MediaUtils.java
index 45422ab..996af84 100644
--- a/media2/media2-session/src/main/java/androidx/media2/session/MediaUtils.java
+++ b/media2/media2-session/src/main/java/androidx/media2/session/MediaUtils.java
@@ -35,6 +35,7 @@
 import static androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_URI;
 import static androidx.media2.common.MediaMetadata.METADATA_KEY_PLAYABLE;
 import static androidx.media2.common.MediaMetadata.METADATA_KEY_TITLE;
+import static androidx.media2.common.MediaMetadata.METADATA_KEY_USER_RATING;
 import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_DESELECT_TRACK;
 import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SELECT_TRACK;
 import static androidx.media2.session.SessionCommand.COMMAND_CODE_PLAYER_SET_SPEED;
@@ -44,6 +45,7 @@
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.media.AudioManager;
 import android.net.Uri;
 import android.os.BadParcelableException;
 import android.os.Build;
@@ -309,15 +311,19 @@
 
     /**
      * Convert a {@link MediaMetadataCompat} from the {@link MediaControllerCompat#getMetadata()}
-     * to a {@link MediaItem}.
+     * and rating type to a {@link MediaItem}.
      */
-    public static MediaItem convertToMediaItem(MediaMetadataCompat metadataCompat) {
+    @Nullable
+    public static MediaItem convertToMediaItem(@Nullable MediaMetadataCompat metadataCompat,
+            int ratingType) {
         if (metadataCompat == null) {
             return null;
         }
         // Item is from the MediaControllerCompat, so forcefully set the playable.
         MediaMetadata.Builder builder = new MediaMetadata.Builder()
-                .putLong(METADATA_KEY_PLAYABLE, 1);
+                .putLong(METADATA_KEY_PLAYABLE, 1)
+                .putRating(METADATA_KEY_USER_RATING,
+                        MediaUtils.convertToRating(RatingCompat.newUnratedRating(ratingType)));
         for (String key : metadataCompat.keySet()) {
             Object value = metadataCompat.getBundle().get(key);
             String metadataKey = METADATA_COMPAT_KEY_TO_METADATA_KEY.containsKey(key)
@@ -346,7 +352,8 @@
     /**
      * Convert a {@link MediaDescriptionCompat} to a {@link MediaItem}.
      */
-    public static MediaItem convertToMediaItem(MediaDescriptionCompat descriptionCompat) {
+    @Nullable
+    public static MediaItem convertToMediaItem(@Nullable MediaDescriptionCompat descriptionCompat) {
         MediaMetadata metadata = convertToMediaMetadata(descriptionCompat, false, true);
         if (metadata == null) {
             return null;
@@ -957,6 +964,29 @@
         return layout;
     }
 
+    /**
+     * Gets the legacy stream type from {@link androidx.media.AudioAttributesCompat}.
+     *
+     * @param attrs audio attributes
+     * @return int legacy stream type from {@link AudioManager}
+     **/
+    public static int getLegacyStreamType(@Nullable AudioAttributesCompat attrs) {
+        int stream;
+        if (attrs == null) {
+            stream = AudioManager.STREAM_MUSIC;
+        } else {
+            stream = attrs.getLegacyStreamType();
+            if (stream == AudioManager.USE_DEFAULT_STREAM_TYPE) {
+                // Usually, AudioAttributesCompat#getLegacyStreamType() does not return
+                // USE_DEFAULT_STREAM_TYPE unless the developer sets it with
+                // AudioAttributesCompat.Builder#setLegacyStreamType().
+                // But for safety, let's convert USE_DEFAULT_STREAM_TYPE to STREAM_MUSIC here.
+                stream = AudioManager.STREAM_MUSIC;
+            }
+        }
+        return stream;
+    }
+
     @SuppressWarnings("ParcelClassLoader")
     static boolean doesBundleHaveCustomParcelable(@NonNull Bundle bundle) {
         // Try writing the bundle to parcel, and read it with framework classloader.
diff --git a/media2/media2-session/src/main/res/values-af/strings.xml b/media2/media2-session/src/main/res/values-af/strings.xml
new file mode 100644
index 0000000..2b218f5
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-af/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Speel tans"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Speel"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Onderbreek"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Slaan oor na vorige item"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Slaan oor na volgende item"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-am/strings.xml b/media2/media2-session/src/main/res/values-am/strings.xml
new file mode 100644
index 0000000..2334a9be
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-am/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"አሁን እየተጫወተ ያለ"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"አጫውት"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"ላፍታ አቁም"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"ወደ ቀዳሚው ንጥል ዝለል"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"ወደ ቀጣዩ ንጥል ዝለል"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-ar/strings.xml b/media2/media2-session/src/main/res/values-ar/strings.xml
new file mode 100644
index 0000000..87acf94
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-ar/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"التعرف التلقائي على الوسائط"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"تشغيل"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"إيقاف مؤقت"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"التخطي إلى العنصر السابق"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"التخطي إلى العنصر التالي"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-as/strings.xml b/media2/media2-session/src/main/res/values-as/strings.xml
new file mode 100644
index 0000000..0f1be72
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-as/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"এতিয়া প্লে’ হৈ আছে"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"প্লে’ কৰক"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"পজ কৰক"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"এটা এৰি তাৰ পূৰ্বৱৰ্তী বস্তুটোলৈ যাওক"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"এটা এৰি তাৰ পৰৱৰ্তী বস্তুটোলৈ যাওক"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-az/strings.xml b/media2/media2-session/src/main/res/values-az/strings.xml
new file mode 100644
index 0000000..fd8b4f2
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-az/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"İndi oxudulur"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Oxudun"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Durdurun"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Əvvəlki elementə keçin"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Növbəti elementə keçin"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-b+sr+Latn/strings.xml b/media2/media2-session/src/main/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..642e80f
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Trenutno svira"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Pusti"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Pauziraj"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Pređi na prethodnu stavku"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Pređi na sledeću stavku"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-be/strings.xml b/media2/media2-session/src/main/res/values-be/strings.xml
new file mode 100644
index 0000000..0f1a9ca
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-be/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Зараз іграе"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Прайграць"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Прыпыніць"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Перайсці да папярэдняга элемента"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Перайсці да наступнага элемента"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-bg/strings.xml b/media2/media2-session/src/main/res/values-bg/strings.xml
new file mode 100644
index 0000000..8727a09
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-bg/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Възпроизвежда се в момента"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Пускане"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Поставяне на пауза"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Преминаване към предишния елемент"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Преминаване към следващия елемент"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-bn/strings.xml b/media2/media2-session/src/main/res/values-bn/strings.xml
new file mode 100644
index 0000000..4a816d0
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-bn/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"এখন চলছে"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"চালান"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"পজ করুন"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"পূর্ববর্তী আইটেমে যান"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"পরবর্তী আইটেমে যান"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-bs/strings.xml b/media2/media2-session/src/main/res/values-bs/strings.xml
new file mode 100644
index 0000000..2354f80
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-bs/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Trenutno se reproducira"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Reproduciranje"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Pauza"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Preskok na prethodnu stavku"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Preskok na sljedeću stavku"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-ca/strings.xml b/media2/media2-session/src/main/res/values-ca/strings.xml
new file mode 100644
index 0000000..a216a5a
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-ca/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Està sonant"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Reprodueix"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Posa en pausa"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Ves a l\'element anterior"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Ves a l\'element següent"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-cs/strings.xml b/media2/media2-session/src/main/res/values-cs/strings.xml
new file mode 100644
index 0000000..c95de94
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-cs/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Přehrává se"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Přehrát"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Pozastavit"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Přejít na předchozí položku"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Přejít na další položku"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-da/strings.xml b/media2/media2-session/src/main/res/values-da/strings.xml
new file mode 100644
index 0000000..b7583eb24
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-da/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Afspilles nu"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Afspil"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Sæt på pause"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Gå til forrige element"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Gå til næste element"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-de/strings.xml b/media2/media2-session/src/main/res/values-de/strings.xml
new file mode 100644
index 0000000..3e9b97d
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-de/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Now Playing"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Wiedergeben"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Pausieren"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Zum vorherigen Titel springen"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Zum nächsten Titel springen"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-el/strings.xml b/media2/media2-session/src/main/res/values-el/strings.xml
new file mode 100644
index 0000000..a7a786f
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-el/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Ακούγεται τώρα"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Αναπαραγωγή"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Παύση"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Μετάβαση στο προηγούμενο στοιχείο"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Μετάβαση στο επόμενο στοιχείο"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-es-rUS/strings.xml b/media2/media2-session/src/main/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..581d257
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-es-rUS/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"En reproducción"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Reproducir"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Pausar"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Ir al elemento anterior"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Ir al siguiente elemento"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-es/strings.xml b/media2/media2-session/src/main/res/values-es/strings.xml
new file mode 100644
index 0000000..f679d62b
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-es/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Está sonando"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Reproducir"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Pausar"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Saltar al elemento anterior"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Saltar al siguiente elemento"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-et/strings.xml b/media2/media2-session/src/main/res/values-et/strings.xml
new file mode 100644
index 0000000..8933aec
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-et/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Hetkel mängimas"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Esitamine"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Peatamine"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Eelmise üksuse juurde liikumine"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Järgmise üksuse juurde liikumine"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-eu/strings.xml b/media2/media2-session/src/main/res/values-eu/strings.xml
new file mode 100644
index 0000000..0d53ac5
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-eu/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Orain erreproduzitzen"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Erreproduzitu"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Pausatu"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Joan aurreko elementura"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Joan hurrengo elementura"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-fa/strings.xml b/media2/media2-session/src/main/res/values-fa/strings.xml
new file mode 100644
index 0000000..a1ef671
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-fa/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"درحال پخش"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"پخش"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"مکث"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"پرش به مورد قبلی"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"پرش به مورد بعدی"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-fi/strings.xml b/media2/media2-session/src/main/res/values-fi/strings.xml
new file mode 100644
index 0000000..bd76c9b
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-fi/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Nyt toistetaan"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Toista"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Keskeytä"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Siirry edelliseen"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Siirry seuraavaan"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-fr-rCA/strings.xml b/media2/media2-session/src/main/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..b039cf3
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-fr-rCA/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"En cours de lecture"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Lire"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Pause"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Passer à l\'élément précédent"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Passer à l\'élément suivant"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-fr/strings.xml b/media2/media2-session/src/main/res/values-fr/strings.xml
new file mode 100644
index 0000000..219bd45
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-fr/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"En écoute"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Lire"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Pause"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Passer à l\'élément précédent"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Passer à l\'élément suivant"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-gl/strings.xml b/media2/media2-session/src/main/res/values-gl/strings.xml
new file mode 100644
index 0000000..bb39015
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-gl/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Reproducindo"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Reproducir"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Pór en pausa"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Omitir o elemento e ir ao anterior"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Omitir o elemento e ir ao seguinte"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-gu/strings.xml b/media2/media2-session/src/main/res/values-gu/strings.xml
new file mode 100644
index 0000000..1e64b22
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-gu/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"હાલમાં ચાલી રહ્યું છે"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"ચલાવો"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"થોભાવો"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"પહેલાંની આઇટમ પર જાઓ"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"આગલી આઇટમ પર જાઓ"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-hi/strings.xml b/media2/media2-session/src/main/res/values-hi/strings.xml
new file mode 100644
index 0000000..774d02c
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-hi/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"अभी चल रहा है"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"चलाएं"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"रोकें"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"पिछले आइटम पर जाएं"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"अगले आइटम पर जाएं"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-hr/strings.xml b/media2/media2-session/src/main/res/values-hr/strings.xml
new file mode 100644
index 0000000..284694f
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-hr/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Sada se reproducira"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Reproduciraj"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Pauziraj"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Preskoči na prethodnu stavku"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Preskoči na sljedeću stavku"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-hu/strings.xml b/media2/media2-session/src/main/res/values-hu/strings.xml
new file mode 100644
index 0000000..bf54bd9
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-hu/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Most játszott tartalom"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Lejátszás"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Szünet"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Ugrás az előző elemre"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Ugrás a következő elemre"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-in/strings.xml b/media2/media2-session/src/main/res/values-in/strings.xml
new file mode 100644
index 0000000..4afd084
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-in/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Sedang diputar"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Putar"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Jeda"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Lewati ke item sebelumnya"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Lewati ke item berikutnya"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-is/strings.xml b/media2/media2-session/src/main/res/values-is/strings.xml
new file mode 100644
index 0000000..b734aff
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-is/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Í spilun"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Spila"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Gera hlé"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Fara í fyrra atriði"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Fara í næsta atriði"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-it/strings.xml b/media2/media2-session/src/main/res/values-it/strings.xml
new file mode 100644
index 0000000..7f52bd2
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-it/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Ora in riproduzione"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Riproduci"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Metti in pausa"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Vai all\'elemento precedente"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Vai all\'elemento successivo"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-iw/strings.xml b/media2/media2-session/src/main/res/values-iw/strings.xml
new file mode 100644
index 0000000..e10750a
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-iw/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"פועל עכשיו"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"הפעלה"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"השהיה"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"דילוג לפריט הקודם"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"דילוג לפריט הבא"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-ja/strings.xml b/media2/media2-session/src/main/res/values-ja/strings.xml
new file mode 100644
index 0000000..4714e31
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-ja/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"再生中"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"再生"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"一時停止"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"前の項目にスキップ"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"次の項目にスキップ"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-kk/strings.xml b/media2/media2-session/src/main/res/values-kk/strings.xml
new file mode 100644
index 0000000..44c6089
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-kk/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Ойнатылуда"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Ойнату"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Кідірту"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Алдыңғы элементке өткізіп жіберу"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Келесі элементке өткізіп жіберу"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-km/strings.xml b/media2/media2-session/src/main/res/values-km/strings.xml
new file mode 100644
index 0000000..ee9817f
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-km/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"កំពុងចាក់"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"ចាក់"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"ផ្អាក"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"រំលងទៅមេឌៀមុន"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"រំលងទៅមេឌៀបន្ទាប់"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-ko/strings.xml b/media2/media2-session/src/main/res/values-ko/strings.xml
new file mode 100644
index 0000000..1b0e13a
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-ko/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"지금 재생 중인 미디어 이름"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"재생"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"일시중지"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"이전 항목으로 건너뛰기"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"다음 항목으로 건너뛰기"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-ky/strings.xml b/media2/media2-session/src/main/res/values-ky/strings.xml
new file mode 100644
index 0000000..6d6af6d
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-ky/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Эмне ойноп жатат?"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Ойнотуу"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Тындыруу"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Мурунку нерсеге өтүү"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Кийинки нерсеге өтүү"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-lo/strings.xml b/media2/media2-session/src/main/res/values-lo/strings.xml
new file mode 100644
index 0000000..cfb99a2
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-lo/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"ກຳລັງຫຼິ້ນ"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"ຫຼິ້ນ"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"ຢຸດຊົ່ວຄາວ"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"ຂ້າມໄປລາຍການກ່ອນໜ້າ"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"ຂ້າມໄປລາຍການຕໍ່ໄປ"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-lt/strings.xml b/media2/media2-session/src/main/res/values-lt/strings.xml
new file mode 100644
index 0000000..c15654c
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-lt/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Dabar leidžiama"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Leisti"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Pristabdyti"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Pereiti prie ankstesnio elemento"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Pereiti prie kito elemento"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-lv/strings.xml b/media2/media2-session/src/main/res/values-lv/strings.xml
new file mode 100644
index 0000000..9d7edff
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-lv/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Tagad atskaņo"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Atskaņot"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Apturēt"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Pāriet uz iepriekšējo vienumu"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Pāriet uz nākamo vienumu"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-mk/strings.xml b/media2/media2-session/src/main/res/values-mk/strings.xml
new file mode 100644
index 0000000..1f3ee24
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-mk/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Now Playing"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Пушти"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Паузирај"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Скокни на претходната ставка"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Скокни на следната ставка"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-ml/strings.xml b/media2/media2-session/src/main/res/values-ml/strings.xml
new file mode 100644
index 0000000..bab4b55
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-ml/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"ഇപ്പോൾ പ്ലേ ചെയ്യുന്നത്"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"പ്ലേ ചെയ്യുക"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"താൽക്കാലികമായി നിർത്തുക"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"മുമ്പത്തെ ഇനത്തിലേക്ക് പോകുക"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"അടുത്ത ഇനത്തിലേക്ക് പോകുക"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-mn/strings.xml b/media2/media2-session/src/main/res/values-mn/strings.xml
new file mode 100644
index 0000000..c963b5e
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-mn/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Одоо тоглуулж байна"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Тоглуулах"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Түр зогсоох"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Өмнөх зүйл рүү алгасах"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Дараагийн зүйл рүү алгасах"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-mr/strings.xml b/media2/media2-session/src/main/res/values-mr/strings.xml
new file mode 100644
index 0000000..cc9584a
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-mr/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"आता प्ले करत आहे"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"प्ले करा"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"थांबवा"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"मागील आयटमवर जा"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"पुढील आयटमवर जा"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-ms/strings.xml b/media2/media2-session/src/main/res/values-ms/strings.xml
new file mode 100644
index 0000000..9e2daa2
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-ms/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Sedang dimainkan"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Main"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Jeda"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Langkau ke item sebelumnya"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Langkau ke item seterusnya"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-my/strings.xml b/media2/media2-session/src/main/res/values-my/strings.xml
new file mode 100644
index 0000000..13710d2
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-my/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Now Playing"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"ဖွင့်ရန်"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"ခဏရပ်ရန်"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"ယခင်တစ်ခုသို့ ပြန်သွားရန်"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"နောက်တစ်ခုသို့ ကျော်ရန်"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-nb/strings.xml b/media2/media2-session/src/main/res/values-nb/strings.xml
new file mode 100644
index 0000000..32bf8aa
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-nb/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Spilles nå"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Spill av"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Sett på pause"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Gå tilbake til det forrige elementet"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Hopp til det neste elementet"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-ne/strings.xml b/media2/media2-session/src/main/res/values-ne/strings.xml
new file mode 100644
index 0000000..111ef1d
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-ne/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Now playing"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"प्ले गर्नुहोस्"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"पज गर्नुहोस्"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"अघिल्लो वस्तुमा जानुहोस्"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"अर्को वस्तुमा जानुहोस्"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-or/strings.xml b/media2/media2-session/src/main/res/values-or/strings.xml
new file mode 100644
index 0000000..93ed416
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-or/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"ଏବେ ଚାଲୁଛି"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"ଚଲାନ୍ତୁ"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"ବିରତ କରନ୍ତୁ"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"ପୂର୍ବବର୍ତ୍ତୀ ଆଇଟମକୁ ଯାଆନ୍ତୁ"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"ପରବର୍ତ୍ତୀ ଆଇଟମକୁ ଯାଆନ୍ତୁ"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-pa/strings.xml b/media2/media2-session/src/main/res/values-pa/strings.xml
new file mode 100644
index 0000000..f504f50
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-pa/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"ਹੁਣੇ ਚੱਲ ਰਿਹਾ ਹੈ"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"ਚਲਾਓ"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"ਰੋਕੋ"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"ਪਿਛਲੀ ਆਈਟਮ \'ਤੇ ਜਾਓ"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"ਅਗਲੀ ਆਈਟਮ \'ਤੇ ਜਾਓ"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-pt-rBR/strings.xml b/media2/media2-session/src/main/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..80311b2
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-pt-rBR/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Tocando agora"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Iniciar"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Pausar"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Voltar para o item anterior"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Pular para o próximo item"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-pt-rPT/strings.xml b/media2/media2-session/src/main/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..b007850
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-pt-rPT/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"A reproduzir"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Reproduzir"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Colocar em pausa"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Ir para o item anterior"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Ir para o item seguinte"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-pt/strings.xml b/media2/media2-session/src/main/res/values-pt/strings.xml
new file mode 100644
index 0000000..80311b2
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-pt/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Tocando agora"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Iniciar"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Pausar"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Voltar para o item anterior"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Pular para o próximo item"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-ro/strings.xml b/media2/media2-session/src/main/res/values-ro/strings.xml
new file mode 100644
index 0000000..0e8c25b
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-ro/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Now Playing"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Redați"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Întrerupeți"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Treceți la elementul anterior"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Treceți la elementul următor"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-ru/strings.xml b/media2/media2-session/src/main/res/values-ru/strings.xml
new file mode 100644
index 0000000..ff54cac
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-ru/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Текущий медиафайл"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Воспроизвести"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Приостановить"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Перейти к предыдущему файлу"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Перейти к следующему файлу"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-sk/strings.xml b/media2/media2-session/src/main/res/values-sk/strings.xml
new file mode 100644
index 0000000..0aab75e
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-sk/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Prehráva sa"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Prehrať"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Pozastaviť"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Preskočiť na predchádzajúcu položku"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Preskočiť na ďalšiu položku"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-sl/strings.xml b/media2/media2-session/src/main/res/values-sl/strings.xml
new file mode 100644
index 0000000..cfa794b
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-sl/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Zdaj se predvaja"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Predvajanje"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Začasna zaustavitev"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Preskok na prejšnji element"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Preskok na naslednji element"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-sr/strings.xml b/media2/media2-session/src/main/res/values-sr/strings.xml
new file mode 100644
index 0000000..8bf6a2c
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-sr/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Тренутно свира"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Пусти"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Паузирај"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Пређи на претходну ставку"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Пређи на следећу ставку"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-sv/strings.xml b/media2/media2-session/src/main/res/values-sv/strings.xml
new file mode 100644
index 0000000..f52d452
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-sv/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Spelas just nu"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Spela upp"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Pausa"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Hoppa till föregående objekt"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Hoppa till nästa objekt"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-sw/strings.xml b/media2/media2-session/src/main/res/values-sw/strings.xml
new file mode 100644
index 0000000..6571355
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-sw/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Inayocheza sasa"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Cheza"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Simamisha"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Rudi kwenye kipengee kilichotangulia"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Nenda kwenye kipengee kinachofuata"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-ta/strings.xml b/media2/media2-session/src/main/res/values-ta/strings.xml
new file mode 100644
index 0000000..29fe641
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-ta/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"இப்போது பிளே செய்வது"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"பிளே செய்யும்"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"இடைநிறுத்தும்"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"முந்தையதற்குச் செல்லும்"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"அடுத்ததற்குச் செல்லும்"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-te/strings.xml b/media2/media2-session/src/main/res/values-te/strings.xml
new file mode 100644
index 0000000..acfa1d9
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-te/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"ప్రస్తుతం ప్లే అవుతున్నది"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"ప్లే చేయి"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"పాజ్ చేయి"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"మునుపటి ఐటెమ్‌కు స్కిప్ చేయి"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"తర్వాత ఐటెమ్‌కు స్కిప్ చేయి"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-th/strings.xml b/media2/media2-session/src/main/res/values-th/strings.xml
new file mode 100644
index 0000000..17136a7
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-th/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"กำลังเล่น"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"เล่น"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"หยุดชั่วคราว"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"ข้ามไปยังรายการก่อนหน้า"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"ข้ามไปยังรายการถัดไป"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-tl/strings.xml b/media2/media2-session/src/main/res/values-tl/strings.xml
new file mode 100644
index 0000000..d56624b
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-tl/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Nagpi-play ngayon"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"I-play"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"I-pause"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Lumaktaw sa nakaraang item"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Lumaktaw sa susunod na item"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-tr/strings.xml b/media2/media2-session/src/main/res/values-tr/strings.xml
new file mode 100644
index 0000000..8eff645
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-tr/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Şimdi oynatılıyor"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Oynat"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Duraklat"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Önceki öğeye atla"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Sonraki öğeye atla"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-uk/strings.xml b/media2/media2-session/src/main/res/values-uk/strings.xml
new file mode 100644
index 0000000..f805eab
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-uk/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Зараз грає"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Відтворити"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Призупинити"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Перейти до попереднього медіафайлу"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Перейти до наступного медіафайлу"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-ur/strings.xml b/media2/media2-session/src/main/res/values-ur/strings.xml
new file mode 100644
index 0000000..5e36503
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-ur/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"ابھی چل رہا ہے"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"چلائیں"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"موقوف کریں"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"پچھلے آئٹم پر جائیں"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"اگلے آئٹم پر جائیں"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-uz/strings.xml b/media2/media2-session/src/main/res/values-uz/strings.xml
new file mode 100644
index 0000000..606c44f
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-uz/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Bu qaysi musiqa"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Ijro"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Pauza"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Avvalgi obyektga oʻtish"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Keyingi obyektga oʻtish"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-vi/strings.xml b/media2/media2-session/src/main/res/values-vi/strings.xml
new file mode 100644
index 0000000..12e0e99
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-vi/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Đang phát"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Phát"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Tạm dừng"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Chuyển về mục trước đó"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Chuyển đến mục tiếp theo"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-zh-rCN/strings.xml b/media2/media2-session/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..19276e6
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"正在播放"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"播放"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"暂停"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"跳至上一项"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"跳至下一项"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-zh-rHK/strings.xml b/media2/media2-session/src/main/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..1f8a61c
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-zh-rHK/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"現正播放"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"播放"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"暫停"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"跳去上一個項目"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"跳去下一個項目"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-zh-rTW/strings.xml b/media2/media2-session/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..b056fa7
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"現正播放"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"播放"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"暫停"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"跳到上一個項目"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"跳到下一個項目"</string>
+</resources>
diff --git a/media2/media2-session/src/main/res/values-zu/strings.xml b/media2/media2-session/src/main/res/values-zu/strings.xml
new file mode 100644
index 0000000..4e42ec2
--- /dev/null
+++ b/media2/media2-session/src/main/res/values-zu/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="default_notification_channel_name" msgid="7213672915724563695">"Okudlala manje"</string>
+    <string name="play_button_content_description" msgid="963503759453979404">"Dlala"</string>
+    <string name="pause_button_content_description" msgid="3510124037191104584">"Phumula"</string>
+    <string name="skip_to_previous_item_button_content_description" msgid="4352480799561670760">"Yeqela entweni yangaphambilini"</string>
+    <string name="skip_to_next_item_button_content_description" msgid="6034072145387511457">"Yeqela entweni elandelayo"</string>
+</resources>
diff --git a/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/CommonConstants.java b/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/CommonConstants.java
index 59c7f28..cf8d619 100644
--- a/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/CommonConstants.java
+++ b/media2/media2-session/version-compat-tests/common/src/main/java/androidx/media2/test/common/CommonConstants.java
@@ -74,6 +74,8 @@
     public static final String KEY_VOLUME_CONTROL_TYPE = "volumeControlType";
     public static final String KEY_VIDEO_SIZE = "videoSize";
     public static final String KEY_TRACK_INFO = "trackInfo";
+    public static final String KEY_SHUFFLE_MODE = "shuffleMode";
+    public static final String KEY_REPEAT_MODE = "repeatMode";
 
     // SessionCompat arguments
     public static final String KEY_SESSION_COMPAT_TOKEN = "sessionCompatToken";
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSession.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSession.java
index 8c0c242..e9aeac1 100644
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSession.java
+++ b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/RemoteMediaSession.java
@@ -28,6 +28,8 @@
 import static androidx.media2.test.common.CommonConstants.KEY_PLAYER_STATE;
 import static androidx.media2.test.common.CommonConstants.KEY_PLAYLIST;
 import static androidx.media2.test.common.CommonConstants.KEY_PLAYLIST_METADATA;
+import static androidx.media2.test.common.CommonConstants.KEY_REPEAT_MODE;
+import static androidx.media2.test.common.CommonConstants.KEY_SHUFFLE_MODE;
 import static androidx.media2.test.common.CommonConstants.KEY_TRACK_INFO;
 import static androidx.media2.test.common.CommonConstants.KEY_VIDEO_SIZE;
 import static androidx.media2.test.common.CommonConstants.KEY_VOLUME_CONTROL_TYPE;
@@ -647,6 +649,16 @@
             return this;
         }
 
+        public MockPlayerConfigBuilder setShuffleMode(int shuffleMode) {
+            mBundle.putInt(KEY_SHUFFLE_MODE, shuffleMode);
+            return this;
+        }
+
+        public MockPlayerConfigBuilder setRepeatMode(int repeatMode) {
+            mBundle.putInt(KEY_REPEAT_MODE, repeatMode);
+            return this;
+        }
+
         public Bundle build() {
             return mBundle;
         }
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCallbackTest.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCallbackTest.java
index 0e09d7b..6ddcc1a 100644
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCallbackTest.java
+++ b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCallbackTest.java
@@ -60,6 +60,7 @@
 import androidx.test.filters.LargeTest;
 import androidx.versionedparcelable.ParcelUtils;
 
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -151,6 +152,7 @@
     }
 
     @Test
+    @Ignore("Throwing fatal exceptions: b/178708442")
     public void getItem_nullResult() throws Exception {
         final String mediaId = MediaBrowserConstants.MEDIA_ID_GET_NULL_ITEM;
 
@@ -171,6 +173,7 @@
     }
 
     @Test
+    @Ignore("Throwing fatal exceptions: b/178708442")
     public void getItem_invalidResult() throws Exception {
         final String mediaId = MediaBrowserConstants.MEDIA_ID_GET_INVALID_ITEM;
 
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java
index 5000ddc..b7d0f5e 100644
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java
+++ b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerCompatCallbackWithMediaSessionTest.java
@@ -25,6 +25,7 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.RatingCompat;
 import android.support.v4.media.session.MediaControllerCompat;
 import android.support.v4.media.session.MediaSessionCompat.QueueItem;
 import android.support.v4.media.session.PlaybackStateCompat;
@@ -35,9 +36,11 @@
 import androidx.media2.common.MediaItem;
 import androidx.media2.common.MediaMetadata;
 import androidx.media2.common.SessionPlayer;
+import androidx.media2.session.HeartRating;
 import androidx.media2.session.MediaSession;
 import androidx.media2.session.MediaUtils;
 import androidx.media2.session.RemoteSessionPlayer;
+import androidx.media2.session.ThumbRating;
 import androidx.media2.test.client.MediaTestUtils;
 import androidx.media2.test.client.RemoteMediaSession;
 import androidx.media2.test.common.PollingCheck;
@@ -50,6 +53,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -86,6 +90,75 @@
     }
 
     @Test
+    public void gettersAfterConnected() throws Exception {
+        int testState = SessionPlayer.PLAYER_STATE_PLAYING;
+        int testBufferingPosition = 1500;
+        float testSpeed = 1.5f;
+        List<MediaItem> testPlaylist = new ArrayList<>();
+        for (int i = 0; i < 3; i++) {
+            testPlaylist.add(new MediaItem.Builder()
+                    .setMetadata(new MediaMetadata.Builder()
+                            .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, "id=" + i)
+                            .putRating(MediaMetadata.METADATA_KEY_USER_RATING, new HeartRating())
+                            .build())
+                    .build());
+        }
+        int testItemIndex = 0;
+        String testPlaylistTitle = "testPlaylistTitle";
+        MediaMetadata testPlaylistMetadata = new MediaMetadata.Builder()
+                .putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, testPlaylistTitle).build();
+        int testShuffleMode = SessionPlayer.SHUFFLE_MODE_ALL;
+        int testRepeatMode = SessionPlayer.SHUFFLE_MODE_GROUP;
+
+        Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder()
+                .setPlayerState(testState)
+                .setBufferedPosition(testBufferingPosition)
+                .setPlaybackSpeed(testSpeed)
+                .setPlaylist(testPlaylist)
+                .setPlaylistMetadata(testPlaylistMetadata)
+                .setCurrentMediaItem(testPlaylist.get(testItemIndex))
+                .setShuffleMode(testShuffleMode)
+                .setRepeatMode(testRepeatMode)
+                .build();
+        mSession.updatePlayer(playerConfig);
+
+        MediaControllerCompat controller =
+                new MediaControllerCompat(mContext, mSession.getCompatToken());
+        CountDownLatch latch = new CountDownLatch(1);
+        controller.registerCallback(
+                new MediaControllerCompat.Callback() {
+                    @Override
+                    public void onSessionReady() {
+                        latch.countDown();
+                    }
+                }, sHandler);
+
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        assertEquals(testState, MediaUtils.convertToPlayerState(controller.getPlaybackState()));
+        assertEquals(testBufferingPosition, controller.getPlaybackState().getBufferedPosition());
+        assertEquals(testSpeed, controller.getPlaybackState().getPlaybackSpeed(), EPSILON);
+
+        assertEquals(testPlaylist.get(testItemIndex).getMediaId(),
+                controller.getMetadata().getString(MediaMetadata.METADATA_KEY_MEDIA_ID));
+
+        List<QueueItem> queue = controller.getQueue();
+        assertNotNull(queue);
+        assertEquals(testPlaylist.size(), queue.size());
+        for (int i = 0; i < testPlaylist.size(); i++) {
+            assertEquals(testPlaylist.get(i).getMediaId(),
+                    queue.get(i).getDescription().getMediaId());
+        }
+        assertEquals(testPlaylistTitle, controller.getQueueTitle().toString());
+        // TODO(b/182255673): Remove this when the relevant fix is released.
+        if (MediaTestUtils.isServiceToT()) {
+            assertEquals(RatingCompat.RATING_HEART, controller.getRatingType());
+            assertEquals(testShuffleMode, controller.getShuffleMode());
+            assertEquals(testRepeatMode, controller.getRepeatMode());
+        }
+    }
+
+    @Test
     public void repeatModeChange() throws Exception {
         int testRepeatMode = SessionPlayer.REPEAT_MODE_GROUP;
 
@@ -154,13 +227,19 @@
         String testPlaylistTitle = "testPlaylistTitle";
         MediaMetadata testPlaylistMetadata = new MediaMetadata.Builder()
                 .putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, testPlaylistTitle).build();
+        int testShuffleMode = SessionPlayer.SHUFFLE_MODE_ALL;
+        int testRepeatMode = SessionPlayer.SHUFFLE_MODE_GROUP;
 
         AtomicReference<PlaybackStateCompat> playbackStateRef = new AtomicReference<>();
         AtomicReference<MediaMetadataCompat> metadataRef = new AtomicReference<>();
         AtomicReference<CharSequence> queueTitleRef = new AtomicReference<>();
+        AtomicInteger shuffleModeRef = new AtomicInteger();
+        AtomicInteger repeatModeRef = new AtomicInteger();
         CountDownLatch latchForPlaybackState = new CountDownLatch(1);
         CountDownLatch latchForMetadata = new CountDownLatch(1);
         CountDownLatch latchForQueue = new CountDownLatch(2);
+        CountDownLatch latchForShuffleMode = new CountDownLatch(1);
+        CountDownLatch latchForRepeatMode = new CountDownLatch(1);
         MediaControllerCompat.Callback callback = new MediaControllerCompat.Callback() {
             @Override
             public void onPlaybackStateChanged(PlaybackStateCompat state) {
@@ -184,6 +263,18 @@
                 queueTitleRef.set(title);
                 latchForQueue.countDown();
             }
+
+            @Override
+            public void onShuffleModeChanged(int shuffleMode) {
+                shuffleModeRef.set(shuffleMode);
+                latchForShuffleMode.countDown();
+            }
+
+            @Override
+            public void onRepeatModeChanged(int repeatMode) {
+                repeatModeRef.set(repeatMode);
+                latchForRepeatMode.countDown();
+            }
         };
         mControllerCompat.registerCallback(callback, sHandler);
 
@@ -194,6 +285,8 @@
                 .setPlaylist(testPlaylist)
                 .setPlaylistMetadata(testPlaylistMetadata)
                 .setCurrentMediaItem(testPlaylist.get(testItemIndex))
+                .setShuffleMode(testShuffleMode)
+                .setRepeatMode(testRepeatMode)
                 .build();
         mSession.updatePlayer(playerConfig);
 
@@ -215,6 +308,14 @@
                     queue.get(i).getDescription().getMediaId());
         }
         assertEquals(testPlaylistTitle, queueTitleRef.get().toString());
+
+        // TODO(b/182255673): Remove this when the relevant fix is released.
+        if (MediaTestUtils.isServiceToT()) {
+            assertTrue(latchForShuffleMode.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertEquals(testShuffleMode, shuffleModeRef.get());
+            assertTrue(latchForRepeatMode.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertEquals(testRepeatMode, repeatModeRef.get());
+        }
     }
 
     @Test
@@ -502,7 +603,9 @@
         long testPosition = 1234;
         String displayTitle = "displayTitle";
         MediaMetadata metadata = new MediaMetadata.Builder()
-                .putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, displayTitle).build();
+                .putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, displayTitle)
+                .putRating(MediaMetadata.METADATA_KEY_USER_RATING, new ThumbRating())
+                .build();
         MediaItem currentMediaItem = new FileMediaItem.Builder(ParcelFileDescriptor.adoptFd(-1))
                 .setMetadata(metadata)
                 .build();
@@ -550,6 +653,7 @@
                     playbackStateRef.get().getActiveQueueItemId());
             assertEquals(MediaUtils.convertToQueueItemId(testItemIndex),
                     mControllerCompat.getPlaybackState().getActiveQueueItemId());
+            assertEquals(RatingCompat.RATING_THUMB_UP_DOWN, mControllerCompat.getRatingType());
         }
     }
 
diff --git a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerLegacyTest.java b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerLegacyTest.java
index e7f8f18..f432881 100644
--- a/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerLegacyTest.java
+++ b/media2/media2-session/version-compat-tests/current/client/src/androidTest/java/androidx/media2/test/client/tests/MediaControllerLegacyTest.java
@@ -36,6 +36,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.RatingCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.media.session.MediaSessionCompat.QueueItem;
 import android.support.v4.media.session.PlaybackStateCompat;
@@ -46,11 +47,13 @@
 import androidx.media.VolumeProviderCompat;
 import androidx.media2.common.MediaItem;
 import androidx.media2.common.MediaMetadata;
+import androidx.media2.common.Rating;
 import androidx.media2.common.SessionPlayer;
 import androidx.media2.session.MediaController;
 import androidx.media2.session.MediaController.ControllerCallback;
 import androidx.media2.session.MediaSession.CommandButton;
 import androidx.media2.session.MediaUtils;
+import androidx.media2.session.PercentageRating;
 import androidx.media2.session.RemoteSessionPlayer;
 import androidx.media2.session.SessionCommand;
 import androidx.media2.session.SessionCommandGroup;
@@ -112,14 +115,19 @@
         final long bufferedPosition = 900000;
         final long timeDiff = 102;
         final float speed = 0.5f;
+        final int shuffleMode = SessionPlayer.SHUFFLE_MODE_GROUP;
+        final int repeatMode = SessionPlayer.REPEAT_MODE_ALL;
         final MediaMetadataCompat metadata = MediaUtils.convertToMediaMetadataCompat(
                 MediaTestUtils.createMetadata());
 
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
-                .setState(PlaybackStateCompat.STATE_PLAYING, position, speed)
-                .setBufferedPosition(bufferedPosition)
-                .build());
+        mSession.setPlaybackState(
+                new PlaybackStateCompat.Builder()
+                        .setState(PlaybackStateCompat.STATE_PLAYING, position, speed)
+                        .setBufferedPosition(bufferedPosition).build());
         mSession.setMetadata(metadata);
+        mSession.setShuffleMode(shuffleMode);
+        mSession.setRepeatMode(repeatMode);
+        mSession.setRatingType(RatingCompat.RATING_PERCENTAGE);
 
         mController = createController(mSession.getSessionToken());
         mController.setTimeDiff(timeDiff);
@@ -133,6 +141,12 @@
                 (double) mController.getCurrentPosition(), 100.0 /* 100 ms */);
         assertEquals(metadata.getDescription().getMediaId(),
                 mController.getCurrentMediaItem().getMediaId());
+        Rating rating = mController.getCurrentMediaItem().getMetadata()
+                .getRating(MediaMetadata.METADATA_KEY_USER_RATING);
+        assertTrue(rating instanceof PercentageRating);
+        assertFalse(rating.isRated());
+        assertEquals(shuffleMode, mController.getShuffleMode());
+        assertEquals(repeatMode, mController.getRepeatMode());
     }
 
     @Test
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaSessionProviderService.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaSessionProviderService.java
index e1305c2..207705e 100644
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaSessionProviderService.java
+++ b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/MediaSessionProviderService.java
@@ -30,6 +30,8 @@
 import static androidx.media2.test.common.CommonConstants.KEY_PLAYER_STATE;
 import static androidx.media2.test.common.CommonConstants.KEY_PLAYLIST;
 import static androidx.media2.test.common.CommonConstants.KEY_PLAYLIST_METADATA;
+import static androidx.media2.test.common.CommonConstants.KEY_REPEAT_MODE;
+import static androidx.media2.test.common.CommonConstants.KEY_SHUFFLE_MODE;
 import static androidx.media2.test.common.CommonConstants.KEY_TRACK_INFO;
 import static androidx.media2.test.common.CommonConstants.KEY_VIDEO_SIZE;
 import static androidx.media2.test.common.CommonConstants.KEY_VOLUME_CONTROL_TYPE;
@@ -221,6 +223,8 @@
                 localPlayer.mCurrentPosition = config.getLong(KEY_CURRENT_POSITION);
                 localPlayer.mBufferedPosition = config.getLong(KEY_BUFFERED_POSITION);
                 localPlayer.mPlaybackSpeed = config.getFloat(KEY_PLAYBACK_SPEED);
+                localPlayer.mShuffleMode = config.getInt(KEY_SHUFFLE_MODE);
+                localPlayer.mRepeatMode = config.getInt(KEY_REPEAT_MODE);
 
                 ParcelImplListSlice listSlice = config.getParcelable(KEY_PLAYLIST);
                 if (listSlice != null) {
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaMetadataTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaMetadataTest.java
index d804729..0c6f8ab 100644
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaMetadataTest.java
+++ b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaMetadataTest.java
@@ -17,6 +17,7 @@
 package androidx.media2.test.service.tests;
 
 import static androidx.media2.common.MediaMetadata.METADATA_KEY_RATING;
+import static androidx.media2.common.MediaMetadata.METADATA_KEY_USER_RATING;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
@@ -65,12 +66,12 @@
         Builder builder = new Builder();
         builder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, title);
         builder.putLong(MediaMetadata.METADATA_KEY_DISC_NUMBER, discNumber);
-        builder.putRating(MediaMetadata.METADATA_KEY_USER_RATING, rating);
+        builder.putRating(METADATA_KEY_USER_RATING, rating);
 
         MediaMetadata metadata = builder.build();
         assertEquals(title, metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE));
         assertEquals(discNumber, metadata.getLong(MediaMetadata.METADATA_KEY_DISC_NUMBER));
-        assertEquals(rating, metadata.getRating(MediaMetadata.METADATA_KEY_USER_RATING));
+        assertEquals(rating, metadata.getRating(METADATA_KEY_USER_RATING));
     }
 
     @Test
@@ -273,7 +274,7 @@
     }
 
     @Test
-    public void mediaUtils_convertToMediaItem() {
+    public void mediaUtils_convertToMediaItem_withoutUserRating() {
         RatingCompat testRating = RatingCompat.newHeartRating(true);
         long testState = MediaDescriptionCompat.STATUS_DOWNLOADING;
         String testCustomKey = "android.media.test";
@@ -284,15 +285,34 @@
                 .putString(testCustomKey, testCustomValue)
                 .build();
 
-        MediaItem item = MediaUtils.convertToMediaItem(testMetadataCompat);
+        MediaItem item = MediaUtils.convertToMediaItem(
+                testMetadataCompat, RatingCompat.RATING_HEART);
         Rating returnedRating = item.getMetadata().getRating(METADATA_KEY_RATING);
         assertTrue(returnedRating instanceof HeartRating);
         assertTrue(returnedRating.isRated());
         assertEquals(testRating.hasHeart(), ((HeartRating) returnedRating).hasHeart());
+        Rating returnedUserRating = item.getMetadata().getRating(METADATA_KEY_USER_RATING);
+        assertTrue(returnedUserRating instanceof HeartRating);
+        assertFalse(returnedUserRating.isRated());
         assertEquals(MediaMetadata.STATUS_DOWNLOADING,
                 item.getMetadata().getLong(MediaMetadata.METADATA_KEY_DOWNLOAD_STATUS));
         assertFalse(item.getMetadata().containsKey(
                 MediaMetadataCompat.METADATA_KEY_DOWNLOAD_STATUS));
         assertEquals(testCustomValue, item.getMetadata().getString(testCustomKey));
     }
+
+    @Test
+    public void mediaUtils_convertToMediaItem_withUserRating() {
+        RatingCompat testRating = RatingCompat.newHeartRating(true);
+        MediaMetadataCompat testMetadataCompat = new MediaMetadataCompat.Builder()
+                .putRating(MediaMetadataCompat.METADATA_KEY_USER_RATING, testRating)
+                .build();
+
+        MediaItem item = MediaUtils.convertToMediaItem(
+                testMetadataCompat, RatingCompat.RATING_HEART);
+        Rating returnedUserRating = item.getMetadata().getRating(METADATA_KEY_USER_RATING);
+        assertTrue(returnedUserRating instanceof HeartRating);
+        assertTrue(returnedUserRating.isRated());
+        assertTrue(((HeartRating) returnedUserRating).hasHeart());
+    }
 }
diff --git a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java
index 1352b13..2b66ba4 100644
--- a/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java
+++ b/media2/media2-session/version-compat-tests/current/service/src/androidTest/java/androidx/media2/test/service/tests/MediaSessionCallbackWithMediaControllerCompatTest.java
@@ -492,8 +492,12 @@
         AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
                 .setLegacyStreamType(stream)
                 .build();
-        mPlayer.setAudioAttributes(attrs);
-        mSession.updatePlayer(mPlayer);
+        MockPlayer player = new MockPlayer(0);
+        player.setAudioAttributes(attrs);
+
+        // Replace with another player rather than setting the audio attribute of the existing
+        // player for making changes to take effect immediately.
+        mSession.updatePlayer(player);
 
         final int originalVolume = mAudioManager.getStreamVolume(stream);
         final int targetVolume = originalVolume == minVolume
@@ -534,8 +538,11 @@
         AudioAttributesCompat attrs = new AudioAttributesCompat.Builder()
                 .setLegacyStreamType(stream)
                 .build();
-        mPlayer.setAudioAttributes(attrs);
-        mSession.updatePlayer(mPlayer);
+        MockPlayer player = new MockPlayer(0);
+        player.setAudioAttributes(attrs);
+        // Replace with another player rather than setting the audio attribute of the existing
+        // player for making changes to take effect immediately.
+        mSession.updatePlayer(player);
 
         final int originalVolume = mAudioManager.getStreamVolume(stream);
         final int direction = originalVolume == minVolume
diff --git a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCallbackTest.java b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCallbackTest.java
index 38a4296..1ddc1c0 100644
--- a/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCallbackTest.java
+++ b/media2/media2-session/version-compat-tests/previous/client/src/androidTest/java/androidx/media2/test/client/tests/MediaBrowserCallbackTest.java
@@ -60,6 +60,7 @@
 import androidx.test.filters.LargeTest;
 import androidx.versionedparcelable.ParcelUtils;
 
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -154,6 +155,7 @@
     }
 
     @Test
+    @Ignore("Throwing fatal exceptions: b/178708442")
     public void getItem_nullResult() throws Exception {
         prepareLooper();
         final String mediaId = MediaBrowserConstants.MEDIA_ID_GET_NULL_ITEM;
@@ -175,6 +177,7 @@
     }
 
     @Test
+    @Ignore("Throwing fatal exceptions: b/178708442")
     public void getItem_invalidResult() throws Exception {
         prepareLooper();
         final String mediaId = MediaBrowserConstants.MEDIA_ID_GET_INVALID_ITEM;
diff --git a/mediarouter/mediarouter/build.gradle b/mediarouter/mediarouter/build.gradle
index 0afec28..993ef6e 100644
--- a/mediarouter/mediarouter/build.gradle
+++ b/mediarouter/mediarouter/build.gradle
@@ -27,6 +27,7 @@
     api("androidx.media:media:1.2.0")
     api(GUAVA_LISTENABLE_FUTURE)
 
+    implementation(project(":core:core"))
     implementation("androidx.appcompat:appcompat:1.1.0")
     implementation("androidx.palette:palette:1.0.0")
     implementation("androidx.recyclerview:recyclerview:1.1.0")
diff --git a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2Test.java b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2Test.java
index 76cf79c..d18c5e8 100644
--- a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2Test.java
+++ b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouter2Test.java
@@ -35,7 +35,6 @@
 import androidx.mediarouter.media.MediaRouter.RouteInfo;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
@@ -128,7 +127,6 @@
     }
 
     @Test
-    @FlakyTest
     @MediumTest
     public void selectFromMr1AndStopFromSystem_unselect() throws Exception {
         CountDownLatch onRouteSelectedLatch = new CountDownLatch(1);
@@ -224,7 +222,6 @@
         }
     }
 
-    @FlakyTest // b/182205261
     @SmallTest
     @Test
     public void setRouterParams_onRouteParamsChangedCalled() throws Exception {
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteButton.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteButton.java
index 8f910d5..e56f791 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteButton.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/app/MediaRouteButton.java
@@ -32,6 +32,7 @@
 import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.ConnectivityManager;
+import android.os.Build;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -44,6 +45,7 @@
 import androidx.appcompat.content.res.AppCompatResources;
 import androidx.appcompat.widget.TooltipCompat;
 import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.core.os.BuildCompat;
 import androidx.core.view.ViewCompat;
 import androidx.fragment.app.FragmentActivity;
 import androidx.fragment.app.FragmentManager;
@@ -394,13 +396,28 @@
      * Returns {@code false} if there was no output switcher.
      */
     private boolean showOutputSwitcher() {
+        boolean result = false;
+        if (BuildCompat.isAtLeastS()) {
+            result = showOutputSwitcherForAndroidSAndAbove();
+            if (!result) {
+                // The intent action and related string constants are changed in S,
+                // however they are not public API yet. Try opening the output switcher with the
+                // old constants for devices that have prior version of the constants.
+                result = showOutputSwitcherForAndroidR();
+            }
+        } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
+            result = showOutputSwitcherForAndroidR();
+        }
+        return result;
+    }
+
+    private boolean showOutputSwitcherForAndroidR() {
         Context context = getContext();
 
         Intent intent = new Intent()
-                .setAction(OutputSwitcherConstants.ACTION_MEDIA_OUTPUT)
-                .putExtra(OutputSwitcherConstants.EXTRA_PACKAGE_NAME, context.getPackageName())
-                .putExtra(OutputSwitcherConstants.KEY_MEDIA_SESSION_TOKEN,
-                        mRouter.getMediaSessionToken());
+                .setAction("com.android.settings.panel.action.MEDIA_OUTPUT")
+                .putExtra("com.android.settings.panel.extra.PACKAGE_NAME", context.getPackageName())
+                .putExtra("key_media_session_token", mRouter.getMediaSessionToken());
 
         PackageManager packageManager = context.getPackageManager();
         List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(intent, 0);
@@ -416,7 +433,33 @@
                 return true;
             }
         }
-        // There was no output switcher.
+        return false;
+    }
+
+    private boolean showOutputSwitcherForAndroidSAndAbove() {
+        Context context = getContext();
+
+        Intent intent = new Intent()
+                .setAction("com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG")
+                .setPackage("com.android.systemui")
+                .putExtra("package_name", context.getPackageName())
+                .putExtra("key_media_session_token", mRouter.getMediaSessionToken());
+
+        PackageManager packageManager = context.getPackageManager();
+        List<ResolveInfo> resolveInfos = packageManager.queryBroadcastReceivers(intent, 0);
+        for (ResolveInfo resolveInfo : resolveInfos) {
+            ActivityInfo activityInfo = resolveInfo.activityInfo;
+            if (activityInfo == null || activityInfo.applicationInfo == null) {
+                continue;
+            }
+            ApplicationInfo appInfo = activityInfo.applicationInfo;
+            if (((ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)
+                    & appInfo.flags) != 0) {
+                context.sendBroadcast(intent);
+                return true;
+            }
+        }
+
         return false;
     }
 
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/app/OutputSwitcherConstants.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/app/OutputSwitcherConstants.java
deleted file mode 100644
index ad28ebb..0000000
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/app/OutputSwitcherConstants.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.mediarouter.app;
-
-/**
- * Constants for opening Output Switcher activity.
- */
-class OutputSwitcherConstants {
-    /**
-     * Copied from MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT.
-     */
-    static final String ACTION_MEDIA_OUTPUT = "com.android.settings.panel.action.MEDIA_OUTPUT";
-
-    /**
-     * Copied from MediaOutputSliceConstants.EXTRA_PACKAGE_NAME.
-     */
-    static final String EXTRA_PACKAGE_NAME = "com.android.settings.panel.extra.PACKAGE_NAME";
-
-    /**
-     * Copied from MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN.
-     */
-    static final String KEY_MEDIA_SESSION_TOKEN = "key_media_session_token";
-
-    private OutputSwitcherConstants() {}
-}
diff --git a/navigation/OWNERS b/navigation/OWNERS
index 1dcd21e..5245ec9 100644
--- a/navigation/OWNERS
+++ b/navigation/OWNERS
@@ -2,3 +2,6 @@
 [email protected]
 [email protected]
 [email protected]
+
+per-file settings.gradle = [email protected], [email protected]
+
diff --git a/navigation/navigation-compose/integration-tests/navigation-demos/lint-baseline.xml b/navigation/navigation-compose/integration-tests/navigation-demos/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/navigation/navigation-compose/integration-tests/navigation-demos/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/navigation/navigation-compose/lint-baseline.xml b/navigation/navigation-compose/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/navigation/navigation-compose/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/navigation/navigation-compose/samples/lint-baseline.xml b/navigation/navigation-compose/samples/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/navigation/navigation-compose/samples/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/navigation/settings.gradle b/navigation/settings.gradle
index 2b1ff64..4b2759e 100644
--- a/navigation/settings.gradle
+++ b/navigation/settings.gradle
@@ -28,7 +28,8 @@
     if (name.startsWith(":internal-testutils-navigation")) return true
     if (name.startsWith(":internal-testutils-runtime")) return true
     if (name.startsWith(":internal-testutils-truth")) return true
-    if (name == ":compose:internal-lint-checks") return true
+    if (name == ":compose:lint:common") return true
+    if (name == ":compose:lint:internal-lint-checks") return true
     return false
 })
 
diff --git a/paging/common/api/3.0.0-beta03.txt b/paging/common/api/3.0.0-beta03.txt
new file mode 100644
index 0000000..0afcd46
--- /dev/null
+++ b/paging/common/api/3.0.0-beta03.txt
@@ -0,0 +1,470 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  public final class CachedPagingDataKt {
+    method @CheckResult public static <T> kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>> cachedIn(kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+  }
+
+  public final class CancelableChannelFlowKt {
+  }
+
+  public final class CombinedLoadStates {
+    ctor public CombinedLoadStates(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append, androidx.paging.LoadStates source, optional androidx.paging.LoadStates? mediator);
+    method public androidx.paging.LoadState getAppend();
+    method public androidx.paging.LoadStates? getMediator();
+    method public androidx.paging.LoadState getPrepend();
+    method public androidx.paging.LoadState getRefresh();
+    method public androidx.paging.LoadStates getSource();
+    property public final androidx.paging.LoadState append;
+    property public final androidx.paging.LoadStates? mediator;
+    property public final androidx.paging.LoadState prepend;
+    property public final androidx.paging.LoadState refresh;
+    property public final androidx.paging.LoadStates source;
+  }
+
+  public abstract class DataSource<Key, Value> {
+    method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+    method @AnyThread public void invalidate();
+    method @WorkerThread public boolean isInvalid();
+    method public <ToValue> androidx.paging.DataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method public <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+    property @WorkerThread public boolean isInvalid;
+  }
+
+  public abstract static class DataSource.Factory<Key, Value> {
+    ctor public DataSource.Factory();
+    method public final kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> asPagingSourceFactory(optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method public final kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> asPagingSourceFactory();
+    method public abstract androidx.paging.DataSource<Key,Value> create();
+    method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+  }
+
+  public static fun interface DataSource.InvalidatedCallback {
+    method @AnyThread public void onInvalidated();
+  }
+
+  public final class FlowExtKt {
+  }
+
+  public final class InvalidatingPagingSourceFactory<Key, Value> implements kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> {
+    ctor public InvalidatingPagingSourceFactory(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    method public void invalidate();
+    method public androidx.paging.PagingSource<Key,Value> invoke();
+  }
+
+  @Deprecated public abstract class ItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+    ctor @Deprecated public ItemKeyedDataSource();
+    method @Deprecated public abstract Key getKey(Value item);
+    method @Deprecated public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+    method @Deprecated public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+    method @Deprecated public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value> callback);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(kotlin.jvm.functions.Function1<? super Value,? extends ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends Value>,? extends java.util.List<? extends ToValue>> function);
+  }
+
+  @Deprecated public abstract static class ItemKeyedDataSource.LoadCallback<Value> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data);
+  }
+
+  @Deprecated public abstract static class ItemKeyedDataSource.LoadInitialCallback<Value> extends androidx.paging.ItemKeyedDataSource.LoadCallback<Value> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount);
+  }
+
+  @Deprecated public static class ItemKeyedDataSource.LoadInitialParams<Key> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadInitialParams(Key? requestedInitialKey, int requestedLoadSize, boolean placeholdersEnabled);
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final Key? requestedInitialKey;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public static class ItemKeyedDataSource.LoadParams<Key> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
+    field @Deprecated public final Key key;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  public final class ItemSnapshotList<T> extends kotlin.collections.AbstractList<T> {
+    ctor public ItemSnapshotList(@IntRange(from=0) int placeholdersBefore, @IntRange(from=0) int placeholdersAfter, java.util.List<? extends T> items);
+    method public T? get(int index);
+    method public java.util.List<T> getItems();
+    method public int getPlaceholdersAfter();
+    method public int getPlaceholdersBefore();
+    method public int getSize();
+    property public final java.util.List<T> items;
+    property public final int placeholdersAfter;
+    property public final int placeholdersBefore;
+    property public int size;
+  }
+
+  public abstract sealed class LoadState {
+    method public final boolean getEndOfPaginationReached();
+    property public final boolean endOfPaginationReached;
+  }
+
+  public static final class LoadState.Error extends androidx.paging.LoadState {
+    ctor public LoadState.Error(Throwable error);
+    method public Throwable getError();
+    property public final Throwable error;
+  }
+
+  public static final class LoadState.Loading extends androidx.paging.LoadState {
+    field public static final androidx.paging.LoadState.Loading INSTANCE;
+  }
+
+  public static final class LoadState.NotLoading extends androidx.paging.LoadState {
+    ctor public LoadState.NotLoading(boolean endOfPaginationReached);
+  }
+
+  public final class LoadStates {
+    ctor public LoadStates(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append);
+    method public androidx.paging.LoadState component1();
+    method public androidx.paging.LoadState component2();
+    method public androidx.paging.LoadState component3();
+    method public androidx.paging.LoadStates copy(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append);
+    method public androidx.paging.LoadState getAppend();
+    method public androidx.paging.LoadState getPrepend();
+    method public androidx.paging.LoadState getRefresh();
+    property public final androidx.paging.LoadState append;
+    property public final androidx.paging.LoadState prepend;
+    property public final androidx.paging.LoadState refresh;
+  }
+
+  public enum LoadType {
+    enum_constant public static final androidx.paging.LoadType APPEND;
+    enum_constant public static final androidx.paging.LoadType PREPEND;
+    enum_constant public static final androidx.paging.LoadType REFRESH;
+  }
+
+  public final class PageFetcherSnapshotKt {
+  }
+
+  @Deprecated public abstract class PageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+    ctor @Deprecated public PageKeyedDataSource();
+    method @Deprecated public abstract void loadAfter(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+    method @Deprecated public abstract void loadBefore(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+    method @Deprecated public abstract void loadInitial(androidx.paging.PageKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.PageKeyedDataSource.LoadInitialCallback<Key,Value> callback);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(kotlin.jvm.functions.Function1<? super Value,? extends ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends Value>,? extends java.util.List<? extends ToValue>> function);
+  }
+
+  @Deprecated public abstract static class PageKeyedDataSource.LoadCallback<Key, Value> {
+    ctor @Deprecated public PageKeyedDataSource.LoadCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, Key? adjacentPageKey);
+  }
+
+  @Deprecated public abstract static class PageKeyedDataSource.LoadInitialCallback<Key, Value> {
+    ctor @Deprecated public PageKeyedDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount, Key? previousPageKey, Key? nextPageKey);
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, Key? previousPageKey, Key? nextPageKey);
+  }
+
+  @Deprecated public static class PageKeyedDataSource.LoadInitialParams<Key> {
+    ctor @Deprecated public PageKeyedDataSource.LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled);
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public static class PageKeyedDataSource.LoadParams<Key> {
+    ctor @Deprecated public PageKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
+    field @Deprecated public final Key key;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public abstract class PagedList<T> extends java.util.AbstractList<T> {
+    method @Deprecated public final void addWeakCallback(java.util.List<? extends T>? previousSnapshot, androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void addWeakCallback(androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void addWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public abstract void detach();
+    method @Deprecated public T? get(int index);
+    method @Deprecated public final androidx.paging.PagedList.Config getConfig();
+    method @Deprecated public final androidx.paging.DataSource<?,T> getDataSource();
+    method @Deprecated public abstract Object? getLastKey();
+    method @Deprecated public final int getLoadedCount();
+    method @Deprecated public final int getPositionOffset();
+    method @Deprecated public int getSize();
+    method @Deprecated public abstract boolean isDetached();
+    method @Deprecated public boolean isImmutable();
+    method @Deprecated public final void loadAround(int index);
+    method @Deprecated public final void removeWeakCallback(androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void removeWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void retry();
+    method @Deprecated public final java.util.List<T> snapshot();
+    property public final androidx.paging.PagedList.Config config;
+    property @Deprecated public final androidx.paging.DataSource<?,T> dataSource;
+    property public abstract boolean isDetached;
+    property public boolean isImmutable;
+    property public abstract Object? lastKey;
+    property public final int loadedCount;
+    property public final int positionOffset;
+    property public int size;
+  }
+
+  @Deprecated @MainThread public abstract static class PagedList.BoundaryCallback<T> {
+    ctor @Deprecated public PagedList.BoundaryCallback();
+    method @Deprecated public void onItemAtEndLoaded(T itemAtEnd);
+    method @Deprecated public void onItemAtFrontLoaded(T itemAtFront);
+    method @Deprecated public void onZeroItemsLoaded();
+  }
+
+  @Deprecated public static final class PagedList.Builder<Key, Value> {
+    ctor @Deprecated public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, int pageSize);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.PagingSource<Key,Value> pagingSource, androidx.paging.PagingSource.LoadResult.Page<Key,Value> initialPage, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.PagingSource<Key,Value> pagingSource, androidx.paging.PagingSource.LoadResult.Page<Key,Value> initialPage, int pageSize);
+    method @Deprecated public androidx.paging.PagedList<Value> build();
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setFetchDispatcher(kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setInitialKey(Key? initialKey);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setNotifyDispatcher(kotlinx.coroutines.CoroutineDispatcher notifyDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor notifyExecutor);
+  }
+
+  @Deprecated public abstract static class PagedList.Callback {
+    ctor @Deprecated public PagedList.Callback();
+    method @Deprecated public abstract void onChanged(int position, int count);
+    method @Deprecated public abstract void onInserted(int position, int count);
+    method @Deprecated public abstract void onRemoved(int position, int count);
+  }
+
+  @Deprecated public static final class PagedList.Config {
+    field @Deprecated public static final int MAX_SIZE_UNBOUNDED = 2147483647; // 0x7fffffff
+    field @Deprecated public final boolean enablePlaceholders;
+    field @Deprecated public final int initialLoadSizeHint;
+    field @Deprecated public final int maxSize;
+    field @Deprecated public final int pageSize;
+    field @Deprecated public final int prefetchDistance;
+  }
+
+  @Deprecated public static final class PagedList.Config.Builder {
+    ctor @Deprecated public PagedList.Config.Builder();
+    method @Deprecated public androidx.paging.PagedList.Config build();
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setEnablePlaceholders(boolean enablePlaceholders);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setInitialLoadSizeHint(@IntRange(from=1) int initialLoadSizeHint);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setMaxSize(@IntRange(from=2) int maxSize);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setPageSize(@IntRange(from=1) int pageSize);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0) int prefetchDistance);
+  }
+
+  public final class PagedListConfigKt {
+  }
+
+  public final class PagedListKt {
+  }
+
+  public final class Pager<Key, Value> {
+    ctor public Pager(androidx.paging.PagingConfig config, optional Key? initialKey, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    ctor public Pager(androidx.paging.PagingConfig config, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    method public kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<Value>> getFlow();
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<Value>> flow;
+  }
+
+  public final class PagingConfig {
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1) int initialLoadSize, optional @IntRange(from=2) int maxSize, optional int jumpThreshold);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1) int initialLoadSize, optional @IntRange(from=2) int maxSize);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1) int initialLoadSize);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance);
+    ctor public PagingConfig(int pageSize);
+    field public static final androidx.paging.PagingConfig.Companion Companion;
+    field public static final int MAX_SIZE_UNBOUNDED = 2147483647; // 0x7fffffff
+    field public final boolean enablePlaceholders;
+    field public final int initialLoadSize;
+    field public final int jumpThreshold;
+    field public final int maxSize;
+    field public final int pageSize;
+    field public final int prefetchDistance;
+  }
+
+  public static final class PagingConfig.Companion {
+  }
+
+  public final class PagingData<T> {
+    method public static <T> androidx.paging.PagingData<T> empty();
+    method public static <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data);
+    field public static final androidx.paging.PagingData.Companion Companion;
+  }
+
+  public static final class PagingData.Companion {
+    method public <T> androidx.paging.PagingData<T> empty();
+    method public <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data);
+  }
+
+  public final class PagingDataTransforms {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,? extends java.lang.Iterable<? extends R>> transform);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertFooterItem(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertFooterItem(androidx.paging.PagingData<T>, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertHeaderItem(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertHeaderItem(androidx.paging.PagingData<T>, T item);
+    method @CheckResult public static <R, T extends R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function2<? super T,? super T,? extends R> generator);
+    method @CheckResult public static <R, T extends R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function2<? super T,? super T,? extends R> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,? extends R> transform);
+  }
+
+  public abstract class PagingSource<Key, Value> {
+    ctor public PagingSource();
+    method public final boolean getInvalid();
+    method public boolean getJumpingSupported();
+    method public boolean getKeyReuseSupported();
+    method public abstract Key? getRefreshKey(androidx.paging.PagingState<Key,Value> state);
+    method public final void invalidate();
+    method public abstract suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public final void registerInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
+    method public final void unregisterInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
+    property public final boolean invalid;
+    property public boolean jumpingSupported;
+    property public boolean keyReuseSupported;
+  }
+
+  public abstract static sealed class PagingSource.LoadParams<Key> {
+    method public abstract Key? getKey();
+    method public final int getLoadSize();
+    method public final boolean getPlaceholdersEnabled();
+    property public abstract Key? key;
+    property public final int loadSize;
+    property public final boolean placeholdersEnabled;
+  }
+
+  public static final class PagingSource.LoadParams.Append<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Append(Key key, int loadSize, boolean placeholdersEnabled);
+    method public Key getKey();
+    property public Key key;
+  }
+
+  public static final class PagingSource.LoadParams.Prepend<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Prepend(Key key, int loadSize, boolean placeholdersEnabled);
+    method public Key getKey();
+    property public Key key;
+  }
+
+  public static final class PagingSource.LoadParams.Refresh<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Refresh(Key? key, int loadSize, boolean placeholdersEnabled);
+    method public Key? getKey();
+    property public Key? key;
+  }
+
+  public abstract static sealed class PagingSource.LoadResult<Key, Value> {
+  }
+
+  public static final class PagingSource.LoadResult.Error<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Error(Throwable throwable);
+    method public Throwable component1();
+    method public androidx.paging.PagingSource.LoadResult.Error<Key,Value> copy(Throwable throwable);
+    method public Throwable getThrowable();
+    property public final Throwable throwable;
+  }
+
+  public static final class PagingSource.LoadResult.Page<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey, optional @IntRange(from=COUNT_UNDEFINED.toLong()) int itemsBefore, optional @IntRange(from=COUNT_UNDEFINED.toLong()) int itemsAfter);
+    ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey);
+    method public java.util.List<Value> component1();
+    method public Key? component2();
+    method public Key? component3();
+    method public int component4();
+    method public int component5();
+    method public androidx.paging.PagingSource.LoadResult.Page<Key,Value> copy(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey, int itemsBefore, int itemsAfter);
+    method public java.util.List<Value> getData();
+    method public int getItemsAfter();
+    method public int getItemsBefore();
+    method public Key? getNextKey();
+    method public Key? getPrevKey();
+    property public final java.util.List<Value> data;
+    property public final int itemsAfter;
+    property public final int itemsBefore;
+    property public final Key? nextKey;
+    property public final Key? prevKey;
+    field public static final int COUNT_UNDEFINED = -2147483648; // 0x80000000
+    field public static final androidx.paging.PagingSource.LoadResult.Page.Companion Companion;
+  }
+
+  public static final class PagingSource.LoadResult.Page.Companion {
+  }
+
+  public final class PagingSourceKt {
+  }
+
+  public final class PagingState<Key, Value> {
+    ctor public PagingState(java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> pages, Integer? anchorPosition, androidx.paging.PagingConfig config, @IntRange(from=0) int leadingPlaceholderCount);
+    method public Value? closestItemToPosition(int anchorPosition);
+    method public androidx.paging.PagingSource.LoadResult.Page<Key,Value>? closestPageToPosition(int anchorPosition);
+    method public Value? firstItemOrNull();
+    method public Integer? getAnchorPosition();
+    method public androidx.paging.PagingConfig getConfig();
+    method public java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> getPages();
+    method public boolean isEmpty();
+    method public Value? lastItemOrNull();
+    property public final Integer? anchorPosition;
+    property public final androidx.paging.PagingConfig config;
+    property public final java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> pages;
+  }
+
+  @Deprecated public abstract class PositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
+    ctor @Deprecated public PositionalDataSource();
+    method @Deprecated public static final int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams params, int totalCount);
+    method @Deprecated public static final int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams params, int initialLoadPosition, int totalCount);
+    method @Deprecated @WorkerThread public abstract void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams params, androidx.paging.PositionalDataSource.LoadInitialCallback<T> callback);
+    method @Deprecated @WorkerThread public abstract void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams params, androidx.paging.PositionalDataSource.LoadRangeCallback<T> callback);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> map(androidx.arch.core.util.Function<T,V> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> map(kotlin.jvm.functions.Function1<? super T,? extends V> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> mapByPage(androidx.arch.core.util.Function<java.util.List<T>,java.util.List<V>> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends T>,? extends java.util.List<? extends V>> function);
+  }
+
+  @Deprecated public abstract static class PositionalDataSource.LoadInitialCallback<T> {
+    ctor @Deprecated public PositionalDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data, int position, int totalCount);
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data, int position);
+  }
+
+  @Deprecated public static class PositionalDataSource.LoadInitialParams {
+    ctor @Deprecated public PositionalDataSource.LoadInitialParams(int requestedStartPosition, int requestedLoadSize, int pageSize, boolean placeholdersEnabled);
+    field @Deprecated public final int pageSize;
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final int requestedLoadSize;
+    field @Deprecated public final int requestedStartPosition;
+  }
+
+  @Deprecated public abstract static class PositionalDataSource.LoadRangeCallback<T> {
+    ctor @Deprecated public PositionalDataSource.LoadRangeCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data);
+  }
+
+  @Deprecated public static class PositionalDataSource.LoadRangeParams {
+    ctor @Deprecated public PositionalDataSource.LoadRangeParams(int startPosition, int loadSize);
+    field @Deprecated public final int loadSize;
+    field @Deprecated public final int startPosition;
+  }
+
+  public final class RemoteMediatorAccessorKt {
+  }
+
+  public final class SeparatorsKt {
+  }
+
+  public final class SimpleChannelFlowKt {
+  }
+
+  public enum TerminalSeparatorType {
+    enum_constant public static final androidx.paging.TerminalSeparatorType FULLY_COMPLETE;
+    enum_constant public static final androidx.paging.TerminalSeparatorType SOURCE_COMPLETE;
+  }
+
+}
+
+package androidx.paging.multicast {
+
+  public final class ChannelManagerKt {
+  }
+
+}
+
diff --git a/paging/common/api/public_plus_experimental_3.0.0-beta03.txt b/paging/common/api/public_plus_experimental_3.0.0-beta03.txt
new file mode 100644
index 0000000..5565ae2
--- /dev/null
+++ b/paging/common/api/public_plus_experimental_3.0.0-beta03.txt
@@ -0,0 +1,507 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  public final class CachedPagingDataKt {
+    method @CheckResult public static <T> kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>> cachedIn(kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+  }
+
+  public final class CancelableChannelFlowKt {
+  }
+
+  public final class CombinedLoadStates {
+    ctor public CombinedLoadStates(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append, androidx.paging.LoadStates source, optional androidx.paging.LoadStates? mediator);
+    method public androidx.paging.LoadState getAppend();
+    method public androidx.paging.LoadStates? getMediator();
+    method public androidx.paging.LoadState getPrepend();
+    method public androidx.paging.LoadState getRefresh();
+    method public androidx.paging.LoadStates getSource();
+    property public final androidx.paging.LoadState append;
+    property public final androidx.paging.LoadStates? mediator;
+    property public final androidx.paging.LoadState prepend;
+    property public final androidx.paging.LoadState refresh;
+    property public final androidx.paging.LoadStates source;
+  }
+
+  public abstract class DataSource<Key, Value> {
+    method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+    method @AnyThread public void invalidate();
+    method @WorkerThread public boolean isInvalid();
+    method public <ToValue> androidx.paging.DataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method public <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+    property @WorkerThread public boolean isInvalid;
+  }
+
+  public abstract static class DataSource.Factory<Key, Value> {
+    ctor public DataSource.Factory();
+    method public final kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> asPagingSourceFactory(optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method public final kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> asPagingSourceFactory();
+    method public abstract androidx.paging.DataSource<Key,Value> create();
+    method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+  }
+
+  public static fun interface DataSource.InvalidatedCallback {
+    method @AnyThread public void onInvalidated();
+  }
+
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public enum DiffingChangePayload {
+    enum_constant public static final androidx.paging.DiffingChangePayload ITEM_TO_PLACEHOLDER;
+    enum_constant public static final androidx.paging.DiffingChangePayload PLACEHOLDER_POSITION_CHANGE;
+    enum_constant public static final androidx.paging.DiffingChangePayload PLACEHOLDER_TO_ITEM;
+  }
+
+  @kotlin.RequiresOptIn public @interface ExperimentalPagingApi {
+  }
+
+  public final class FlowExtKt {
+  }
+
+  public final class InvalidatingPagingSourceFactory<Key, Value> implements kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> {
+    ctor public InvalidatingPagingSourceFactory(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    method public void invalidate();
+    method public androidx.paging.PagingSource<Key,Value> invoke();
+  }
+
+  @Deprecated public abstract class ItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+    ctor @Deprecated public ItemKeyedDataSource();
+    method @Deprecated public abstract Key getKey(Value item);
+    method @Deprecated public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+    method @Deprecated public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+    method @Deprecated public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value> callback);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(kotlin.jvm.functions.Function1<? super Value,? extends ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends Value>,? extends java.util.List<? extends ToValue>> function);
+  }
+
+  @Deprecated public abstract static class ItemKeyedDataSource.LoadCallback<Value> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data);
+  }
+
+  @Deprecated public abstract static class ItemKeyedDataSource.LoadInitialCallback<Value> extends androidx.paging.ItemKeyedDataSource.LoadCallback<Value> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount);
+  }
+
+  @Deprecated public static class ItemKeyedDataSource.LoadInitialParams<Key> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadInitialParams(Key? requestedInitialKey, int requestedLoadSize, boolean placeholdersEnabled);
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final Key? requestedInitialKey;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public static class ItemKeyedDataSource.LoadParams<Key> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
+    field @Deprecated public final Key key;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  public final class ItemSnapshotList<T> extends kotlin.collections.AbstractList<T> {
+    ctor public ItemSnapshotList(@IntRange(from=0) int placeholdersBefore, @IntRange(from=0) int placeholdersAfter, java.util.List<? extends T> items);
+    method public T? get(int index);
+    method public java.util.List<T> getItems();
+    method public int getPlaceholdersAfter();
+    method public int getPlaceholdersBefore();
+    method public int getSize();
+    property public final java.util.List<T> items;
+    property public final int placeholdersAfter;
+    property public final int placeholdersBefore;
+    property public int size;
+  }
+
+  public abstract sealed class LoadState {
+    method public final boolean getEndOfPaginationReached();
+    property public final boolean endOfPaginationReached;
+  }
+
+  public static final class LoadState.Error extends androidx.paging.LoadState {
+    ctor public LoadState.Error(Throwable error);
+    method public Throwable getError();
+    property public final Throwable error;
+  }
+
+  public static final class LoadState.Loading extends androidx.paging.LoadState {
+    field public static final androidx.paging.LoadState.Loading INSTANCE;
+  }
+
+  public static final class LoadState.NotLoading extends androidx.paging.LoadState {
+    ctor public LoadState.NotLoading(boolean endOfPaginationReached);
+  }
+
+  public final class LoadStates {
+    ctor public LoadStates(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append);
+    method public androidx.paging.LoadState component1();
+    method public androidx.paging.LoadState component2();
+    method public androidx.paging.LoadState component3();
+    method public androidx.paging.LoadStates copy(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append);
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public inline void forEach(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> op);
+    method public androidx.paging.LoadState getAppend();
+    method public androidx.paging.LoadState getPrepend();
+    method public androidx.paging.LoadState getRefresh();
+    property public final androidx.paging.LoadState append;
+    property public final androidx.paging.LoadState prepend;
+    property public final androidx.paging.LoadState refresh;
+  }
+
+  public enum LoadType {
+    enum_constant public static final androidx.paging.LoadType APPEND;
+    enum_constant public static final androidx.paging.LoadType PREPEND;
+    enum_constant public static final androidx.paging.LoadType REFRESH;
+  }
+
+  public final class PageFetcherSnapshotKt {
+  }
+
+  @Deprecated public abstract class PageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+    ctor @Deprecated public PageKeyedDataSource();
+    method @Deprecated public abstract void loadAfter(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+    method @Deprecated public abstract void loadBefore(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+    method @Deprecated public abstract void loadInitial(androidx.paging.PageKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.PageKeyedDataSource.LoadInitialCallback<Key,Value> callback);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(kotlin.jvm.functions.Function1<? super Value,? extends ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends Value>,? extends java.util.List<? extends ToValue>> function);
+  }
+
+  @Deprecated public abstract static class PageKeyedDataSource.LoadCallback<Key, Value> {
+    ctor @Deprecated public PageKeyedDataSource.LoadCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, Key? adjacentPageKey);
+  }
+
+  @Deprecated public abstract static class PageKeyedDataSource.LoadInitialCallback<Key, Value> {
+    ctor @Deprecated public PageKeyedDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount, Key? previousPageKey, Key? nextPageKey);
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, Key? previousPageKey, Key? nextPageKey);
+  }
+
+  @Deprecated public static class PageKeyedDataSource.LoadInitialParams<Key> {
+    ctor @Deprecated public PageKeyedDataSource.LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled);
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public static class PageKeyedDataSource.LoadParams<Key> {
+    ctor @Deprecated public PageKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
+    field @Deprecated public final Key key;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public abstract class PagedList<T> extends java.util.AbstractList<T> {
+    method @Deprecated public final void addWeakCallback(java.util.List<? extends T>? previousSnapshot, androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void addWeakCallback(androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void addWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public abstract void detach();
+    method @Deprecated public T? get(int index);
+    method @Deprecated public final androidx.paging.PagedList.Config getConfig();
+    method @Deprecated public final androidx.paging.DataSource<?,T> getDataSource();
+    method @Deprecated public abstract Object? getLastKey();
+    method @Deprecated public final int getLoadedCount();
+    method @Deprecated public final int getPositionOffset();
+    method @Deprecated public int getSize();
+    method @Deprecated public abstract boolean isDetached();
+    method @Deprecated public boolean isImmutable();
+    method @Deprecated public final void loadAround(int index);
+    method @Deprecated public final void removeWeakCallback(androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void removeWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void retry();
+    method @Deprecated public final java.util.List<T> snapshot();
+    property public final androidx.paging.PagedList.Config config;
+    property @Deprecated public final androidx.paging.DataSource<?,T> dataSource;
+    property public abstract boolean isDetached;
+    property public boolean isImmutable;
+    property public abstract Object? lastKey;
+    property public final int loadedCount;
+    property public final int positionOffset;
+    property public int size;
+  }
+
+  @Deprecated @MainThread public abstract static class PagedList.BoundaryCallback<T> {
+    ctor @Deprecated public PagedList.BoundaryCallback();
+    method @Deprecated public void onItemAtEndLoaded(T itemAtEnd);
+    method @Deprecated public void onItemAtFrontLoaded(T itemAtFront);
+    method @Deprecated public void onZeroItemsLoaded();
+  }
+
+  @Deprecated public static final class PagedList.Builder<Key, Value> {
+    ctor @Deprecated public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, int pageSize);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.PagingSource<Key,Value> pagingSource, androidx.paging.PagingSource.LoadResult.Page<Key,Value> initialPage, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.PagingSource<Key,Value> pagingSource, androidx.paging.PagingSource.LoadResult.Page<Key,Value> initialPage, int pageSize);
+    method @Deprecated public androidx.paging.PagedList<Value> build();
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setFetchDispatcher(kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setInitialKey(Key? initialKey);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setNotifyDispatcher(kotlinx.coroutines.CoroutineDispatcher notifyDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor notifyExecutor);
+  }
+
+  @Deprecated public abstract static class PagedList.Callback {
+    ctor @Deprecated public PagedList.Callback();
+    method @Deprecated public abstract void onChanged(int position, int count);
+    method @Deprecated public abstract void onInserted(int position, int count);
+    method @Deprecated public abstract void onRemoved(int position, int count);
+  }
+
+  @Deprecated public static final class PagedList.Config {
+    field @Deprecated public static final int MAX_SIZE_UNBOUNDED = 2147483647; // 0x7fffffff
+    field @Deprecated public final boolean enablePlaceholders;
+    field @Deprecated public final int initialLoadSizeHint;
+    field @Deprecated public final int maxSize;
+    field @Deprecated public final int pageSize;
+    field @Deprecated public final int prefetchDistance;
+  }
+
+  @Deprecated public static final class PagedList.Config.Builder {
+    ctor @Deprecated public PagedList.Config.Builder();
+    method @Deprecated public androidx.paging.PagedList.Config build();
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setEnablePlaceholders(boolean enablePlaceholders);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setInitialLoadSizeHint(@IntRange(from=1) int initialLoadSizeHint);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setMaxSize(@IntRange(from=2) int maxSize);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setPageSize(@IntRange(from=1) int pageSize);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0) int prefetchDistance);
+  }
+
+  public final class PagedListConfigKt {
+  }
+
+  public final class PagedListKt {
+  }
+
+  public final class Pager<Key, Value> {
+    ctor @androidx.paging.ExperimentalPagingApi public Pager(androidx.paging.PagingConfig config, optional Key? initialKey, androidx.paging.RemoteMediator<Key,Value>? remoteMediator, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    ctor public Pager(androidx.paging.PagingConfig config, optional Key? initialKey, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    ctor public Pager(androidx.paging.PagingConfig config, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    method public kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<Value>> getFlow();
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<Value>> flow;
+  }
+
+  public final class PagingConfig {
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1) int initialLoadSize, optional @IntRange(from=2) int maxSize, optional int jumpThreshold);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1) int initialLoadSize, optional @IntRange(from=2) int maxSize);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1) int initialLoadSize);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance);
+    ctor public PagingConfig(int pageSize);
+    field public static final androidx.paging.PagingConfig.Companion Companion;
+    field public static final int MAX_SIZE_UNBOUNDED = 2147483647; // 0x7fffffff
+    field public final boolean enablePlaceholders;
+    field public final int initialLoadSize;
+    field public final int jumpThreshold;
+    field public final int maxSize;
+    field public final int pageSize;
+    field public final int prefetchDistance;
+  }
+
+  public static final class PagingConfig.Companion {
+  }
+
+  public final class PagingData<T> {
+    method public static <T> androidx.paging.PagingData<T> empty();
+    method public static <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data);
+    field public static final androidx.paging.PagingData.Companion Companion;
+  }
+
+  public static final class PagingData.Companion {
+    method public <T> androidx.paging.PagingData<T> empty();
+    method public <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data);
+  }
+
+  public final class PagingDataTransforms {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,? extends java.lang.Iterable<? extends R>> transform);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertFooterItem(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertFooterItem(androidx.paging.PagingData<T>, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertHeaderItem(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertHeaderItem(androidx.paging.PagingData<T>, T item);
+    method @CheckResult public static <R, T extends R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function2<? super T,? super T,? extends R> generator);
+    method @CheckResult public static <R, T extends R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function2<? super T,? super T,? extends R> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,? extends R> transform);
+  }
+
+  public abstract class PagingSource<Key, Value> {
+    ctor public PagingSource();
+    method public final boolean getInvalid();
+    method public boolean getJumpingSupported();
+    method public boolean getKeyReuseSupported();
+    method public abstract Key? getRefreshKey(androidx.paging.PagingState<Key,Value> state);
+    method public final void invalidate();
+    method public abstract suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public final void registerInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
+    method public final void unregisterInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
+    property public final boolean invalid;
+    property public boolean jumpingSupported;
+    property public boolean keyReuseSupported;
+  }
+
+  public abstract static sealed class PagingSource.LoadParams<Key> {
+    method public abstract Key? getKey();
+    method public final int getLoadSize();
+    method public final boolean getPlaceholdersEnabled();
+    property public abstract Key? key;
+    property public final int loadSize;
+    property public final boolean placeholdersEnabled;
+  }
+
+  public static final class PagingSource.LoadParams.Append<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Append(Key key, int loadSize, boolean placeholdersEnabled);
+    method public Key getKey();
+    property public Key key;
+  }
+
+  public static final class PagingSource.LoadParams.Prepend<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Prepend(Key key, int loadSize, boolean placeholdersEnabled);
+    method public Key getKey();
+    property public Key key;
+  }
+
+  public static final class PagingSource.LoadParams.Refresh<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Refresh(Key? key, int loadSize, boolean placeholdersEnabled);
+    method public Key? getKey();
+    property public Key? key;
+  }
+
+  public abstract static sealed class PagingSource.LoadResult<Key, Value> {
+  }
+
+  public static final class PagingSource.LoadResult.Error<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Error(Throwable throwable);
+    method public Throwable component1();
+    method public androidx.paging.PagingSource.LoadResult.Error<Key,Value> copy(Throwable throwable);
+    method public Throwable getThrowable();
+    property public final Throwable throwable;
+  }
+
+  public static final class PagingSource.LoadResult.Page<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey, optional @IntRange(from=COUNT_UNDEFINED.toLong()) int itemsBefore, optional @IntRange(from=COUNT_UNDEFINED.toLong()) int itemsAfter);
+    ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey);
+    method public java.util.List<Value> component1();
+    method public Key? component2();
+    method public Key? component3();
+    method public int component4();
+    method public int component5();
+    method public androidx.paging.PagingSource.LoadResult.Page<Key,Value> copy(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey, int itemsBefore, int itemsAfter);
+    method public java.util.List<Value> getData();
+    method public int getItemsAfter();
+    method public int getItemsBefore();
+    method public Key? getNextKey();
+    method public Key? getPrevKey();
+    property public final java.util.List<Value> data;
+    property public final int itemsAfter;
+    property public final int itemsBefore;
+    property public final Key? nextKey;
+    property public final Key? prevKey;
+    field public static final int COUNT_UNDEFINED = -2147483648; // 0x80000000
+    field public static final androidx.paging.PagingSource.LoadResult.Page.Companion Companion;
+  }
+
+  public static final class PagingSource.LoadResult.Page.Companion {
+  }
+
+  public final class PagingSourceKt {
+  }
+
+  public final class PagingState<Key, Value> {
+    ctor public PagingState(java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> pages, Integer? anchorPosition, androidx.paging.PagingConfig config, @IntRange(from=0) int leadingPlaceholderCount);
+    method public Value? closestItemToPosition(int anchorPosition);
+    method public androidx.paging.PagingSource.LoadResult.Page<Key,Value>? closestPageToPosition(int anchorPosition);
+    method public Value? firstItemOrNull();
+    method public Integer? getAnchorPosition();
+    method public androidx.paging.PagingConfig getConfig();
+    method public java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> getPages();
+    method public boolean isEmpty();
+    method public Value? lastItemOrNull();
+    property public final Integer? anchorPosition;
+    property public final androidx.paging.PagingConfig config;
+    property public final java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> pages;
+  }
+
+  @Deprecated public abstract class PositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
+    ctor @Deprecated public PositionalDataSource();
+    method @Deprecated public static final int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams params, int totalCount);
+    method @Deprecated public static final int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams params, int initialLoadPosition, int totalCount);
+    method @Deprecated @WorkerThread public abstract void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams params, androidx.paging.PositionalDataSource.LoadInitialCallback<T> callback);
+    method @Deprecated @WorkerThread public abstract void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams params, androidx.paging.PositionalDataSource.LoadRangeCallback<T> callback);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> map(androidx.arch.core.util.Function<T,V> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> map(kotlin.jvm.functions.Function1<? super T,? extends V> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> mapByPage(androidx.arch.core.util.Function<java.util.List<T>,java.util.List<V>> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends T>,? extends java.util.List<? extends V>> function);
+  }
+
+  @Deprecated public abstract static class PositionalDataSource.LoadInitialCallback<T> {
+    ctor @Deprecated public PositionalDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data, int position, int totalCount);
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data, int position);
+  }
+
+  @Deprecated public static class PositionalDataSource.LoadInitialParams {
+    ctor @Deprecated public PositionalDataSource.LoadInitialParams(int requestedStartPosition, int requestedLoadSize, int pageSize, boolean placeholdersEnabled);
+    field @Deprecated public final int pageSize;
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final int requestedLoadSize;
+    field @Deprecated public final int requestedStartPosition;
+  }
+
+  @Deprecated public abstract static class PositionalDataSource.LoadRangeCallback<T> {
+    ctor @Deprecated public PositionalDataSource.LoadRangeCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data);
+  }
+
+  @Deprecated public static class PositionalDataSource.LoadRangeParams {
+    ctor @Deprecated public PositionalDataSource.LoadRangeParams(int startPosition, int loadSize);
+    field @Deprecated public final int loadSize;
+    field @Deprecated public final int startPosition;
+  }
+
+  @androidx.paging.ExperimentalPagingApi public abstract class RemoteMediator<Key, Value> {
+    ctor public RemoteMediator();
+    method public suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction> $completion);
+    method public abstract suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult> p);
+  }
+
+  public enum RemoteMediator.InitializeAction {
+    enum_constant public static final androidx.paging.RemoteMediator.InitializeAction LAUNCH_INITIAL_REFRESH;
+    enum_constant public static final androidx.paging.RemoteMediator.InitializeAction SKIP_INITIAL_REFRESH;
+  }
+
+  public abstract static sealed class RemoteMediator.MediatorResult {
+  }
+
+  public static final class RemoteMediator.MediatorResult.Error extends androidx.paging.RemoteMediator.MediatorResult {
+    ctor public RemoteMediator.MediatorResult.Error(Throwable throwable);
+    method public Throwable getThrowable();
+    property public final Throwable throwable;
+  }
+
+  public static final class RemoteMediator.MediatorResult.Success extends androidx.paging.RemoteMediator.MediatorResult {
+    ctor public RemoteMediator.MediatorResult.Success(boolean endOfPaginationReached);
+    method public boolean endOfPaginationReached();
+    property public final boolean endOfPaginationReached;
+  }
+
+  public final class RemoteMediatorAccessorKt {
+  }
+
+  public final class SeparatorsKt {
+  }
+
+  public final class SimpleChannelFlowKt {
+  }
+
+  public enum TerminalSeparatorType {
+    enum_constant public static final androidx.paging.TerminalSeparatorType FULLY_COMPLETE;
+    enum_constant public static final androidx.paging.TerminalSeparatorType SOURCE_COMPLETE;
+  }
+
+}
+
+package androidx.paging.multicast {
+
+  public final class ChannelManagerKt {
+  }
+
+}
+
diff --git a/paging/common/api/public_plus_experimental_current.txt b/paging/common/api/public_plus_experimental_current.txt
index 9ae0302..5565ae2 100644
--- a/paging/common/api/public_plus_experimental_current.txt
+++ b/paging/common/api/public_plus_experimental_current.txt
@@ -45,6 +45,12 @@
     method @AnyThread public void onInvalidated();
   }
 
+  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public enum DiffingChangePayload {
+    enum_constant public static final androidx.paging.DiffingChangePayload ITEM_TO_PLACEHOLDER;
+    enum_constant public static final androidx.paging.DiffingChangePayload PLACEHOLDER_POSITION_CHANGE;
+    enum_constant public static final androidx.paging.DiffingChangePayload PLACEHOLDER_TO_ITEM;
+  }
+
   @kotlin.RequiresOptIn public @interface ExperimentalPagingApi {
   }
 
diff --git a/paging/common/api/restricted_3.0.0-beta03.txt b/paging/common/api/restricted_3.0.0-beta03.txt
new file mode 100644
index 0000000..0afcd46
--- /dev/null
+++ b/paging/common/api/restricted_3.0.0-beta03.txt
@@ -0,0 +1,470 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  public final class CachedPagingDataKt {
+    method @CheckResult public static <T> kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>> cachedIn(kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+  }
+
+  public final class CancelableChannelFlowKt {
+  }
+
+  public final class CombinedLoadStates {
+    ctor public CombinedLoadStates(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append, androidx.paging.LoadStates source, optional androidx.paging.LoadStates? mediator);
+    method public androidx.paging.LoadState getAppend();
+    method public androidx.paging.LoadStates? getMediator();
+    method public androidx.paging.LoadState getPrepend();
+    method public androidx.paging.LoadState getRefresh();
+    method public androidx.paging.LoadStates getSource();
+    property public final androidx.paging.LoadState append;
+    property public final androidx.paging.LoadStates? mediator;
+    property public final androidx.paging.LoadState prepend;
+    property public final androidx.paging.LoadState refresh;
+    property public final androidx.paging.LoadStates source;
+  }
+
+  public abstract class DataSource<Key, Value> {
+    method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+    method @AnyThread public void invalidate();
+    method @WorkerThread public boolean isInvalid();
+    method public <ToValue> androidx.paging.DataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method public <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+    property @WorkerThread public boolean isInvalid;
+  }
+
+  public abstract static class DataSource.Factory<Key, Value> {
+    ctor public DataSource.Factory();
+    method public final kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> asPagingSourceFactory(optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method public final kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> asPagingSourceFactory();
+    method public abstract androidx.paging.DataSource<Key,Value> create();
+    method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+  }
+
+  public static fun interface DataSource.InvalidatedCallback {
+    method @AnyThread public void onInvalidated();
+  }
+
+  public final class FlowExtKt {
+  }
+
+  public final class InvalidatingPagingSourceFactory<Key, Value> implements kotlin.jvm.functions.Function0<androidx.paging.PagingSource<Key,Value>> {
+    ctor public InvalidatingPagingSourceFactory(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    method public void invalidate();
+    method public androidx.paging.PagingSource<Key,Value> invoke();
+  }
+
+  @Deprecated public abstract class ItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+    ctor @Deprecated public ItemKeyedDataSource();
+    method @Deprecated public abstract Key getKey(Value item);
+    method @Deprecated public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+    method @Deprecated public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+    method @Deprecated public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value> callback);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(kotlin.jvm.functions.Function1<? super Value,? extends ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @Deprecated public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends Value>,? extends java.util.List<? extends ToValue>> function);
+  }
+
+  @Deprecated public abstract static class ItemKeyedDataSource.LoadCallback<Value> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data);
+  }
+
+  @Deprecated public abstract static class ItemKeyedDataSource.LoadInitialCallback<Value> extends androidx.paging.ItemKeyedDataSource.LoadCallback<Value> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount);
+  }
+
+  @Deprecated public static class ItemKeyedDataSource.LoadInitialParams<Key> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadInitialParams(Key? requestedInitialKey, int requestedLoadSize, boolean placeholdersEnabled);
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final Key? requestedInitialKey;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public static class ItemKeyedDataSource.LoadParams<Key> {
+    ctor @Deprecated public ItemKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
+    field @Deprecated public final Key key;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  public final class ItemSnapshotList<T> extends kotlin.collections.AbstractList<T> {
+    ctor public ItemSnapshotList(@IntRange(from=0) int placeholdersBefore, @IntRange(from=0) int placeholdersAfter, java.util.List<? extends T> items);
+    method public T? get(int index);
+    method public java.util.List<T> getItems();
+    method public int getPlaceholdersAfter();
+    method public int getPlaceholdersBefore();
+    method public int getSize();
+    property public final java.util.List<T> items;
+    property public final int placeholdersAfter;
+    property public final int placeholdersBefore;
+    property public int size;
+  }
+
+  public abstract sealed class LoadState {
+    method public final boolean getEndOfPaginationReached();
+    property public final boolean endOfPaginationReached;
+  }
+
+  public static final class LoadState.Error extends androidx.paging.LoadState {
+    ctor public LoadState.Error(Throwable error);
+    method public Throwable getError();
+    property public final Throwable error;
+  }
+
+  public static final class LoadState.Loading extends androidx.paging.LoadState {
+    field public static final androidx.paging.LoadState.Loading INSTANCE;
+  }
+
+  public static final class LoadState.NotLoading extends androidx.paging.LoadState {
+    ctor public LoadState.NotLoading(boolean endOfPaginationReached);
+  }
+
+  public final class LoadStates {
+    ctor public LoadStates(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append);
+    method public androidx.paging.LoadState component1();
+    method public androidx.paging.LoadState component2();
+    method public androidx.paging.LoadState component3();
+    method public androidx.paging.LoadStates copy(androidx.paging.LoadState refresh, androidx.paging.LoadState prepend, androidx.paging.LoadState append);
+    method public androidx.paging.LoadState getAppend();
+    method public androidx.paging.LoadState getPrepend();
+    method public androidx.paging.LoadState getRefresh();
+    property public final androidx.paging.LoadState append;
+    property public final androidx.paging.LoadState prepend;
+    property public final androidx.paging.LoadState refresh;
+  }
+
+  public enum LoadType {
+    enum_constant public static final androidx.paging.LoadType APPEND;
+    enum_constant public static final androidx.paging.LoadType PREPEND;
+    enum_constant public static final androidx.paging.LoadType REFRESH;
+  }
+
+  public final class PageFetcherSnapshotKt {
+  }
+
+  @Deprecated public abstract class PageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+    ctor @Deprecated public PageKeyedDataSource();
+    method @Deprecated public abstract void loadAfter(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+    method @Deprecated public abstract void loadBefore(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+    method @Deprecated public abstract void loadInitial(androidx.paging.PageKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.PageKeyedDataSource.LoadInitialCallback<Key,Value> callback);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(kotlin.jvm.functions.Function1<? super Value,? extends ToValue> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+    method @Deprecated public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends Value>,? extends java.util.List<? extends ToValue>> function);
+  }
+
+  @Deprecated public abstract static class PageKeyedDataSource.LoadCallback<Key, Value> {
+    ctor @Deprecated public PageKeyedDataSource.LoadCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, Key? adjacentPageKey);
+  }
+
+  @Deprecated public abstract static class PageKeyedDataSource.LoadInitialCallback<Key, Value> {
+    ctor @Deprecated public PageKeyedDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount, Key? previousPageKey, Key? nextPageKey);
+    method @Deprecated public abstract void onResult(java.util.List<? extends Value> data, Key? previousPageKey, Key? nextPageKey);
+  }
+
+  @Deprecated public static class PageKeyedDataSource.LoadInitialParams<Key> {
+    ctor @Deprecated public PageKeyedDataSource.LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled);
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public static class PageKeyedDataSource.LoadParams<Key> {
+    ctor @Deprecated public PageKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
+    field @Deprecated public final Key key;
+    field @Deprecated public final int requestedLoadSize;
+  }
+
+  @Deprecated public abstract class PagedList<T> extends java.util.AbstractList<T> {
+    method @Deprecated public final void addWeakCallback(java.util.List<? extends T>? previousSnapshot, androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void addWeakCallback(androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void addWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public abstract void detach();
+    method @Deprecated public T? get(int index);
+    method @Deprecated public final androidx.paging.PagedList.Config getConfig();
+    method @Deprecated public final androidx.paging.DataSource<?,T> getDataSource();
+    method @Deprecated public abstract Object? getLastKey();
+    method @Deprecated public final int getLoadedCount();
+    method @Deprecated public final int getPositionOffset();
+    method @Deprecated public int getSize();
+    method @Deprecated public abstract boolean isDetached();
+    method @Deprecated public boolean isImmutable();
+    method @Deprecated public final void loadAround(int index);
+    method @Deprecated public final void removeWeakCallback(androidx.paging.PagedList.Callback callback);
+    method @Deprecated public final void removeWeakLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void retry();
+    method @Deprecated public final java.util.List<T> snapshot();
+    property public final androidx.paging.PagedList.Config config;
+    property @Deprecated public final androidx.paging.DataSource<?,T> dataSource;
+    property public abstract boolean isDetached;
+    property public boolean isImmutable;
+    property public abstract Object? lastKey;
+    property public final int loadedCount;
+    property public final int positionOffset;
+    property public int size;
+  }
+
+  @Deprecated @MainThread public abstract static class PagedList.BoundaryCallback<T> {
+    ctor @Deprecated public PagedList.BoundaryCallback();
+    method @Deprecated public void onItemAtEndLoaded(T itemAtEnd);
+    method @Deprecated public void onItemAtFrontLoaded(T itemAtFront);
+    method @Deprecated public void onZeroItemsLoaded();
+  }
+
+  @Deprecated public static final class PagedList.Builder<Key, Value> {
+    ctor @Deprecated public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, int pageSize);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.PagingSource<Key,Value> pagingSource, androidx.paging.PagingSource.LoadResult.Page<Key,Value> initialPage, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public PagedList.Builder(androidx.paging.PagingSource<Key,Value> pagingSource, androidx.paging.PagingSource.LoadResult.Page<Key,Value> initialPage, int pageSize);
+    method @Deprecated public androidx.paging.PagedList<Value> build();
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setFetchDispatcher(kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setInitialKey(Key? initialKey);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setNotifyDispatcher(kotlinx.coroutines.CoroutineDispatcher notifyDispatcher);
+    method @Deprecated public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor notifyExecutor);
+  }
+
+  @Deprecated public abstract static class PagedList.Callback {
+    ctor @Deprecated public PagedList.Callback();
+    method @Deprecated public abstract void onChanged(int position, int count);
+    method @Deprecated public abstract void onInserted(int position, int count);
+    method @Deprecated public abstract void onRemoved(int position, int count);
+  }
+
+  @Deprecated public static final class PagedList.Config {
+    field @Deprecated public static final int MAX_SIZE_UNBOUNDED = 2147483647; // 0x7fffffff
+    field @Deprecated public final boolean enablePlaceholders;
+    field @Deprecated public final int initialLoadSizeHint;
+    field @Deprecated public final int maxSize;
+    field @Deprecated public final int pageSize;
+    field @Deprecated public final int prefetchDistance;
+  }
+
+  @Deprecated public static final class PagedList.Config.Builder {
+    ctor @Deprecated public PagedList.Config.Builder();
+    method @Deprecated public androidx.paging.PagedList.Config build();
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setEnablePlaceholders(boolean enablePlaceholders);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setInitialLoadSizeHint(@IntRange(from=1) int initialLoadSizeHint);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setMaxSize(@IntRange(from=2) int maxSize);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setPageSize(@IntRange(from=1) int pageSize);
+    method @Deprecated public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0) int prefetchDistance);
+  }
+
+  public final class PagedListConfigKt {
+  }
+
+  public final class PagedListKt {
+  }
+
+  public final class Pager<Key, Value> {
+    ctor public Pager(androidx.paging.PagingConfig config, optional Key? initialKey, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    ctor public Pager(androidx.paging.PagingConfig config, kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory);
+    method public kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<Value>> getFlow();
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<Value>> flow;
+  }
+
+  public final class PagingConfig {
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1) int initialLoadSize, optional @IntRange(from=2) int maxSize, optional int jumpThreshold);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1) int initialLoadSize, optional @IntRange(from=2) int maxSize);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders, optional @IntRange(from=1) int initialLoadSize);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance, optional boolean enablePlaceholders);
+    ctor public PagingConfig(int pageSize, optional @IntRange(from=0) int prefetchDistance);
+    ctor public PagingConfig(int pageSize);
+    field public static final androidx.paging.PagingConfig.Companion Companion;
+    field public static final int MAX_SIZE_UNBOUNDED = 2147483647; // 0x7fffffff
+    field public final boolean enablePlaceholders;
+    field public final int initialLoadSize;
+    field public final int jumpThreshold;
+    field public final int maxSize;
+    field public final int pageSize;
+    field public final int prefetchDistance;
+  }
+
+  public static final class PagingConfig.Companion {
+  }
+
+  public final class PagingData<T> {
+    method public static <T> androidx.paging.PagingData<T> empty();
+    method public static <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data);
+    field public static final androidx.paging.PagingData.Companion Companion;
+  }
+
+  public static final class PagingData.Companion {
+    method public <T> androidx.paging.PagingData<T> empty();
+    method public <T> androidx.paging.PagingData<T> from(java.util.List<? extends T> data);
+  }
+
+  public final class PagingDataTransforms {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,? extends java.lang.Iterable<? extends R>> transform);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertFooterItem(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertFooterItem(androidx.paging.PagingData<T>, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertHeaderItem(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, T item);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> insertHeaderItem(androidx.paging.PagingData<T>, T item);
+    method @CheckResult public static <R, T extends R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, optional androidx.paging.TerminalSeparatorType terminalSeparatorType, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function2<? super T,? super T,? extends R> generator);
+    method @CheckResult public static <R, T extends R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function2<? super T,? super T,? extends R> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, java.util.concurrent.Executor executor, kotlin.jvm.functions.Function1<? super T,? extends R> transform);
+  }
+
+  public abstract class PagingSource<Key, Value> {
+    ctor public PagingSource();
+    method public final boolean getInvalid();
+    method public boolean getJumpingSupported();
+    method public boolean getKeyReuseSupported();
+    method public abstract Key? getRefreshKey(androidx.paging.PagingState<Key,Value> state);
+    method public final void invalidate();
+    method public abstract suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public final void registerInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
+    method public final void unregisterInvalidatedCallback(kotlin.jvm.functions.Function0<kotlin.Unit> onInvalidatedCallback);
+    property public final boolean invalid;
+    property public boolean jumpingSupported;
+    property public boolean keyReuseSupported;
+  }
+
+  public abstract static sealed class PagingSource.LoadParams<Key> {
+    method public abstract Key? getKey();
+    method public final int getLoadSize();
+    method public final boolean getPlaceholdersEnabled();
+    property public abstract Key? key;
+    property public final int loadSize;
+    property public final boolean placeholdersEnabled;
+  }
+
+  public static final class PagingSource.LoadParams.Append<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Append(Key key, int loadSize, boolean placeholdersEnabled);
+    method public Key getKey();
+    property public Key key;
+  }
+
+  public static final class PagingSource.LoadParams.Prepend<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Prepend(Key key, int loadSize, boolean placeholdersEnabled);
+    method public Key getKey();
+    property public Key key;
+  }
+
+  public static final class PagingSource.LoadParams.Refresh<Key> extends androidx.paging.PagingSource.LoadParams<Key> {
+    ctor public PagingSource.LoadParams.Refresh(Key? key, int loadSize, boolean placeholdersEnabled);
+    method public Key? getKey();
+    property public Key? key;
+  }
+
+  public abstract static sealed class PagingSource.LoadResult<Key, Value> {
+  }
+
+  public static final class PagingSource.LoadResult.Error<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Error(Throwable throwable);
+    method public Throwable component1();
+    method public androidx.paging.PagingSource.LoadResult.Error<Key,Value> copy(Throwable throwable);
+    method public Throwable getThrowable();
+    property public final Throwable throwable;
+  }
+
+  public static final class PagingSource.LoadResult.Page<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey, optional @IntRange(from=COUNT_UNDEFINED.toLong()) int itemsBefore, optional @IntRange(from=COUNT_UNDEFINED.toLong()) int itemsAfter);
+    ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey);
+    method public java.util.List<Value> component1();
+    method public Key? component2();
+    method public Key? component3();
+    method public int component4();
+    method public int component5();
+    method public androidx.paging.PagingSource.LoadResult.Page<Key,Value> copy(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey, int itemsBefore, int itemsAfter);
+    method public java.util.List<Value> getData();
+    method public int getItemsAfter();
+    method public int getItemsBefore();
+    method public Key? getNextKey();
+    method public Key? getPrevKey();
+    property public final java.util.List<Value> data;
+    property public final int itemsAfter;
+    property public final int itemsBefore;
+    property public final Key? nextKey;
+    property public final Key? prevKey;
+    field public static final int COUNT_UNDEFINED = -2147483648; // 0x80000000
+    field public static final androidx.paging.PagingSource.LoadResult.Page.Companion Companion;
+  }
+
+  public static final class PagingSource.LoadResult.Page.Companion {
+  }
+
+  public final class PagingSourceKt {
+  }
+
+  public final class PagingState<Key, Value> {
+    ctor public PagingState(java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> pages, Integer? anchorPosition, androidx.paging.PagingConfig config, @IntRange(from=0) int leadingPlaceholderCount);
+    method public Value? closestItemToPosition(int anchorPosition);
+    method public androidx.paging.PagingSource.LoadResult.Page<Key,Value>? closestPageToPosition(int anchorPosition);
+    method public Value? firstItemOrNull();
+    method public Integer? getAnchorPosition();
+    method public androidx.paging.PagingConfig getConfig();
+    method public java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> getPages();
+    method public boolean isEmpty();
+    method public Value? lastItemOrNull();
+    property public final Integer? anchorPosition;
+    property public final androidx.paging.PagingConfig config;
+    property public final java.util.List<androidx.paging.PagingSource.LoadResult.Page<Key,Value>> pages;
+  }
+
+  @Deprecated public abstract class PositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
+    ctor @Deprecated public PositionalDataSource();
+    method @Deprecated public static final int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams params, int totalCount);
+    method @Deprecated public static final int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams params, int initialLoadPosition, int totalCount);
+    method @Deprecated @WorkerThread public abstract void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams params, androidx.paging.PositionalDataSource.LoadInitialCallback<T> callback);
+    method @Deprecated @WorkerThread public abstract void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams params, androidx.paging.PositionalDataSource.LoadRangeCallback<T> callback);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> map(androidx.arch.core.util.Function<T,V> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> map(kotlin.jvm.functions.Function1<? super T,? extends V> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> mapByPage(androidx.arch.core.util.Function<java.util.List<T>,java.util.List<V>> function);
+    method @Deprecated public final <V> androidx.paging.PositionalDataSource<V> mapByPage(kotlin.jvm.functions.Function1<? super java.util.List<? extends T>,? extends java.util.List<? extends V>> function);
+  }
+
+  @Deprecated public abstract static class PositionalDataSource.LoadInitialCallback<T> {
+    ctor @Deprecated public PositionalDataSource.LoadInitialCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data, int position, int totalCount);
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data, int position);
+  }
+
+  @Deprecated public static class PositionalDataSource.LoadInitialParams {
+    ctor @Deprecated public PositionalDataSource.LoadInitialParams(int requestedStartPosition, int requestedLoadSize, int pageSize, boolean placeholdersEnabled);
+    field @Deprecated public final int pageSize;
+    field @Deprecated public final boolean placeholdersEnabled;
+    field @Deprecated public final int requestedLoadSize;
+    field @Deprecated public final int requestedStartPosition;
+  }
+
+  @Deprecated public abstract static class PositionalDataSource.LoadRangeCallback<T> {
+    ctor @Deprecated public PositionalDataSource.LoadRangeCallback();
+    method @Deprecated public abstract void onResult(java.util.List<? extends T> data);
+  }
+
+  @Deprecated public static class PositionalDataSource.LoadRangeParams {
+    ctor @Deprecated public PositionalDataSource.LoadRangeParams(int startPosition, int loadSize);
+    field @Deprecated public final int loadSize;
+    field @Deprecated public final int startPosition;
+  }
+
+  public final class RemoteMediatorAccessorKt {
+  }
+
+  public final class SeparatorsKt {
+  }
+
+  public final class SimpleChannelFlowKt {
+  }
+
+  public enum TerminalSeparatorType {
+    enum_constant public static final androidx.paging.TerminalSeparatorType FULLY_COMPLETE;
+    enum_constant public static final androidx.paging.TerminalSeparatorType SOURCE_COMPLETE;
+  }
+
+}
+
+package androidx.paging.multicast {
+
+  public final class ChannelManagerKt {
+  }
+
+}
+
diff --git a/paging/common/ktx/api/3.0.0-beta03.txt b/paging/common/ktx/api/3.0.0-beta03.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/common/ktx/api/3.0.0-beta03.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/common/ktx/api/public_plus_experimental_3.0.0-beta03.txt b/paging/common/ktx/api/public_plus_experimental_3.0.0-beta03.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/common/ktx/api/public_plus_experimental_3.0.0-beta03.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/common/ktx/api/restricted_3.0.0-beta03.txt b/paging/common/ktx/api/restricted_3.0.0-beta03.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/common/ktx/api/restricted_3.0.0-beta03.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/common/lint-baseline.xml b/paging/common/lint-baseline.xml
deleted file mode 100644
index 297ae16..0000000
--- a/paging/common/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" version="4.2.0-beta02">
-
-</issues>
diff --git a/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt b/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
index 19562a5..abdf285 100644
--- a/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
@@ -21,7 +21,7 @@
 import kotlinx.coroutines.CoroutineScope
 
 /**
- * InitialPagedList is an empty placeholder that's sent at the front of a stream of [PagedList].
+ * [InitialPagedList] is an empty placeholder that's sent at the front of a stream of [PagedList].
  *
  * It's used solely for listening to [LoadType.REFRESH] loading events, and retrying
  * any errors that occur during initial load.
diff --git a/paging/common/src/main/kotlin/androidx/paging/InitialPagingSource.kt b/paging/common/src/main/kotlin/androidx/paging/InitialPagingSource.kt
new file mode 100644
index 0000000..348a3e9
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/InitialPagingSource.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.paging
+
+import androidx.annotation.RestrictTo
+
+/**
+ * [InitialPagingSource] is a placeholder [PagingSource] implementation that only returns empty
+ * pages and `null` keys.
+ *
+ * It should be used exclusively in [InitialPagedList] since it is required to be supplied
+ * synchronously, but [DataSource.Factory] should run on background thread.
+ *
+ * @suppress
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+public class InitialPagingSource<K : Any, V : Any> : PagingSource<K, V>() {
+    override suspend fun load(params: LoadParams<K>): LoadResult<K, V> {
+        return LoadResult.Page.empty()
+    }
+
+    override fun getRefreshKey(state: PagingState<K, V>): K? {
+        return null
+    }
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt b/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
index 7f6b49e..16cb5fc 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingDataDiffer.kt
@@ -373,3 +373,17 @@
     public fun onInserted(position: Int, count: Int)
     public fun onRemoved(position: Int, count: Int)
 }
+
+/**
+ * Payloads used to dispatch change events.
+ * Could become a public API post 3.0 in case developers want to handle it more effectively.
+ *
+ * Sending these change payloads is critical for the common case where DefaultItemAnimator won't
+ * animate them and re-use the same view holder if possible.
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public enum class DiffingChangePayload {
+    ITEM_TO_PLACEHOLDER,
+    PLACEHOLDER_TO_ITEM,
+    PLACEHOLDER_POSITION_CHANGE
+}
\ No newline at end of file
diff --git a/paging/guava/api/3.0.0-beta03.txt b/paging/guava/api/3.0.0-beta03.txt
new file mode 100644
index 0000000..00d8c77
--- /dev/null
+++ b/paging/guava/api/3.0.0-beta03.txt
@@ -0,0 +1,29 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  public final class AdjacentItems<T> {
+    ctor public AdjacentItems(T? before, T? after);
+    method public T? component1();
+    method public T? component2();
+    method public androidx.paging.AdjacentItems<T> copy(T? before, T? after);
+    method public T? getAfter();
+    method public T? getBefore();
+    property public final T? after;
+    property public final T? before;
+  }
+
+  public abstract class ListenableFuturePagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public ListenableFuturePagingSource();
+    method public suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> p, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> $completion);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagingSource.LoadResult<Key,Value>> loadFuture(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+  public final class PagingDataFutures {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,java.lang.Boolean> predicate, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,java.lang.Iterable<R>> transform, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<androidx.paging.AdjacentItems<T>,R> generator, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,R> transform, java.util.concurrent.Executor executor);
+  }
+
+}
+
diff --git a/paging/guava/api/public_plus_experimental_3.0.0-beta03.txt b/paging/guava/api/public_plus_experimental_3.0.0-beta03.txt
new file mode 100644
index 0000000..af6e822
--- /dev/null
+++ b/paging/guava/api/public_plus_experimental_3.0.0-beta03.txt
@@ -0,0 +1,37 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  public final class AdjacentItems<T> {
+    ctor public AdjacentItems(T? before, T? after);
+    method public T? component1();
+    method public T? component2();
+    method public androidx.paging.AdjacentItems<T> copy(T? before, T? after);
+    method public T? getAfter();
+    method public T? getBefore();
+    property public final T? after;
+    property public final T? before;
+  }
+
+  public abstract class ListenableFuturePagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public ListenableFuturePagingSource();
+    method public suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> p, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> $completion);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagingSource.LoadResult<Key,Value>> loadFuture(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+  @androidx.paging.ExperimentalPagingApi public abstract class ListenableFutureRemoteMediator<Key, Value> extends androidx.paging.RemoteMediator<Key,Value> {
+    ctor public ListenableFutureRemoteMediator();
+    method public final suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction> p);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.paging.RemoteMediator.InitializeAction> initializeFuture();
+    method public final suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult> p);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.RemoteMediator.MediatorResult> loadFuture(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state);
+  }
+
+  public final class PagingDataFutures {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,java.lang.Boolean> predicate, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,java.lang.Iterable<R>> transform, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<androidx.paging.AdjacentItems<T>,R> generator, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,R> transform, java.util.concurrent.Executor executor);
+  }
+
+}
+
diff --git a/paging/guava/api/res-3.0.0-beta03.txt b/paging/guava/api/res-3.0.0-beta03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/guava/api/res-3.0.0-beta03.txt
diff --git a/paging/guava/api/restricted_3.0.0-beta03.txt b/paging/guava/api/restricted_3.0.0-beta03.txt
new file mode 100644
index 0000000..00d8c77
--- /dev/null
+++ b/paging/guava/api/restricted_3.0.0-beta03.txt
@@ -0,0 +1,29 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  public final class AdjacentItems<T> {
+    ctor public AdjacentItems(T? before, T? after);
+    method public T? component1();
+    method public T? component2();
+    method public androidx.paging.AdjacentItems<T> copy(T? before, T? after);
+    method public T? getAfter();
+    method public T? getBefore();
+    property public final T? after;
+    property public final T? before;
+  }
+
+  public abstract class ListenableFuturePagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public ListenableFuturePagingSource();
+    method public suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> p, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> $completion);
+    method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagingSource.LoadResult<Key,Value>> loadFuture(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+  public final class PagingDataFutures {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,java.lang.Boolean> predicate, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,java.lang.Iterable<R>> transform, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<androidx.paging.AdjacentItems<T>,R> generator, java.util.concurrent.Executor executor);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, com.google.common.util.concurrent.AsyncFunction<T,R> transform, java.util.concurrent.Executor executor);
+  }
+
+}
+
diff --git a/paging/paging-compose/integration-tests/paging-demos/lint-baseline.xml b/paging/paging-compose/integration-tests/paging-demos/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/paging/paging-compose/integration-tests/paging-demos/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/paging/paging-compose/lint-baseline.xml b/paging/paging-compose/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/paging/paging-compose/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/paging/paging-compose/samples/lint-baseline.xml b/paging/paging-compose/samples/lint-baseline.xml
deleted file mode 100644
index 53ae1f6..0000000
--- a/paging/paging-compose/samples/lint-baseline.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-</issues>
diff --git a/paging/runtime/api/3.0.0-beta03.txt b/paging/runtime/api/3.0.0-beta03.txt
new file mode 100644
index 0000000..b886d2e
--- /dev/null
+++ b/paging/runtime/api/3.0.0-beta03.txt
@@ -0,0 +1,131 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  @Deprecated public class AsyncPagedListDiffer<T> {
+    ctor @Deprecated public AsyncPagedListDiffer(androidx.recyclerview.widget.RecyclerView.Adapter<?> adapter, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    ctor @Deprecated public AsyncPagedListDiffer(androidx.recyclerview.widget.ListUpdateCallback listUpdateCallback, androidx.recyclerview.widget.AsyncDifferConfig<T> config);
+    method @Deprecated public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void addPagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
+    method @Deprecated public final void addPagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>,? super androidx.paging.PagedList<T>,kotlin.Unit> callback);
+    method @Deprecated public androidx.paging.PagedList<T>? getCurrentList();
+    method @Deprecated public T? getItem(int index);
+    method @Deprecated public int getItemCount();
+    method @Deprecated public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void removePagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
+    method @Deprecated public final void removePagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>,? super androidx.paging.PagedList<T>,kotlin.Unit> callback);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList, Runnable? commitCallback);
+    property public androidx.paging.PagedList<T>? currentList;
+    property public int itemCount;
+  }
+
+  @Deprecated public static interface AsyncPagedListDiffer.PagedListListener<T> {
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? previousList, androidx.paging.PagedList<T>? currentList);
+  }
+
+  public final class AsyncPagingDataDiffer<T> {
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher, optional kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
+    method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public T? getItem(@IntRange(from=0) int index);
+    method public int getItemCount();
+    method public kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public T? peek(@IntRange(from=0) int index);
+    method public void refresh();
+    method public void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public void retry();
+    method public androidx.paging.ItemSnapshotList<T> snapshot();
+    method public suspend Object? submitData(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public void submitData(androidx.lifecycle.Lifecycle lifecycle, androidx.paging.PagingData<T> pagingData);
+    property public final int itemCount;
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+  }
+
+  @Deprecated public final class LivePagedListBuilder<Key, Value> {
+    ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    ctor @Deprecated public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    method @Deprecated public androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> build();
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+  }
+
+  public final class LivePagedListKt {
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional kotlinx.coroutines.CoroutineScope coroutineScope, optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional kotlinx.coroutines.CoroutineScope coroutineScope, optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+  }
+
+  public abstract class LoadStateAdapter<VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor public LoadStateAdapter();
+    method public boolean displayLoadStateAsItem(androidx.paging.LoadState loadState);
+    method public final int getItemCount();
+    method public final int getItemViewType(int position);
+    method public final androidx.paging.LoadState getLoadState();
+    method public int getStateViewType(androidx.paging.LoadState loadState);
+    method public final void onBindViewHolder(VH holder, int position);
+    method public abstract void onBindViewHolder(VH holder, androidx.paging.LoadState loadState);
+    method public final VH onCreateViewHolder(android.view.ViewGroup parent, int viewType);
+    method public abstract VH onCreateViewHolder(android.view.ViewGroup parent, androidx.paging.LoadState loadState);
+    method public final void setLoadState(androidx.paging.LoadState loadState);
+    property public final androidx.paging.LoadState loadState;
+  }
+
+  public final class NullPaddedListDiffHelperKt {
+  }
+
+  @Deprecated public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor @Deprecated protected PagedListAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    ctor @Deprecated protected PagedListAdapter(androidx.recyclerview.widget.AsyncDifferConfig<T> config);
+    method @Deprecated public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public androidx.paging.PagedList<T>? getCurrentList();
+    method @Deprecated protected T? getItem(int position);
+    method @Deprecated public int getItemCount();
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? currentList);
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? previousList, androidx.paging.PagedList<T>? currentList);
+    method @Deprecated public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList, Runnable? commitCallback);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateFooter(androidx.paging.LoadStateAdapter<?> footer);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeader(androidx.paging.LoadStateAdapter<?> header);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeaderAndFooter(androidx.paging.LoadStateAdapter<?> header, androidx.paging.LoadStateAdapter<?> footer);
+    property public androidx.paging.PagedList<T>? currentList;
+  }
+
+  public abstract class PagingDataAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher, optional kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method protected final T? getItem(@IntRange(from=0) int position);
+    method public int getItemCount();
+    method public final long getItemId(int position);
+    method public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public final T? peek(@IntRange(from=0) int index);
+    method public final void refresh();
+    method public final void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void retry();
+    method public final void setHasStableIds(boolean hasStableIds);
+    method public final androidx.paging.ItemSnapshotList<T> snapshot();
+    method public final suspend Object? submitData(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public final void submitData(androidx.lifecycle.Lifecycle lifecycle, androidx.paging.PagingData<T> pagingData);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateFooter(androidx.paging.LoadStateAdapter<?> footer);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeader(androidx.paging.LoadStateAdapter<?> header);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeaderAndFooter(androidx.paging.LoadStateAdapter<?> header, androidx.paging.LoadStateAdapter<?> footer);
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+  }
+
+  public final class PagingLiveData {
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, androidx.lifecycle.Lifecycle lifecycle);
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, androidx.lifecycle.ViewModel viewModel);
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagingData<Value>> getLiveData(androidx.paging.Pager<Key,Value>);
+  }
+
+}
+
diff --git a/paging/runtime/api/public_plus_experimental_3.0.0-beta03.txt b/paging/runtime/api/public_plus_experimental_3.0.0-beta03.txt
new file mode 100644
index 0000000..b886d2e
--- /dev/null
+++ b/paging/runtime/api/public_plus_experimental_3.0.0-beta03.txt
@@ -0,0 +1,131 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  @Deprecated public class AsyncPagedListDiffer<T> {
+    ctor @Deprecated public AsyncPagedListDiffer(androidx.recyclerview.widget.RecyclerView.Adapter<?> adapter, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    ctor @Deprecated public AsyncPagedListDiffer(androidx.recyclerview.widget.ListUpdateCallback listUpdateCallback, androidx.recyclerview.widget.AsyncDifferConfig<T> config);
+    method @Deprecated public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void addPagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
+    method @Deprecated public final void addPagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>,? super androidx.paging.PagedList<T>,kotlin.Unit> callback);
+    method @Deprecated public androidx.paging.PagedList<T>? getCurrentList();
+    method @Deprecated public T? getItem(int index);
+    method @Deprecated public int getItemCount();
+    method @Deprecated public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void removePagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
+    method @Deprecated public final void removePagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>,? super androidx.paging.PagedList<T>,kotlin.Unit> callback);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList, Runnable? commitCallback);
+    property public androidx.paging.PagedList<T>? currentList;
+    property public int itemCount;
+  }
+
+  @Deprecated public static interface AsyncPagedListDiffer.PagedListListener<T> {
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? previousList, androidx.paging.PagedList<T>? currentList);
+  }
+
+  public final class AsyncPagingDataDiffer<T> {
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher, optional kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
+    method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public T? getItem(@IntRange(from=0) int index);
+    method public int getItemCount();
+    method public kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public T? peek(@IntRange(from=0) int index);
+    method public void refresh();
+    method public void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public void retry();
+    method public androidx.paging.ItemSnapshotList<T> snapshot();
+    method public suspend Object? submitData(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public void submitData(androidx.lifecycle.Lifecycle lifecycle, androidx.paging.PagingData<T> pagingData);
+    property public final int itemCount;
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+  }
+
+  @Deprecated public final class LivePagedListBuilder<Key, Value> {
+    ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    ctor @Deprecated public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    method @Deprecated public androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> build();
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+  }
+
+  public final class LivePagedListKt {
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional kotlinx.coroutines.CoroutineScope coroutineScope, optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional kotlinx.coroutines.CoroutineScope coroutineScope, optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+  }
+
+  public abstract class LoadStateAdapter<VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor public LoadStateAdapter();
+    method public boolean displayLoadStateAsItem(androidx.paging.LoadState loadState);
+    method public final int getItemCount();
+    method public final int getItemViewType(int position);
+    method public final androidx.paging.LoadState getLoadState();
+    method public int getStateViewType(androidx.paging.LoadState loadState);
+    method public final void onBindViewHolder(VH holder, int position);
+    method public abstract void onBindViewHolder(VH holder, androidx.paging.LoadState loadState);
+    method public final VH onCreateViewHolder(android.view.ViewGroup parent, int viewType);
+    method public abstract VH onCreateViewHolder(android.view.ViewGroup parent, androidx.paging.LoadState loadState);
+    method public final void setLoadState(androidx.paging.LoadState loadState);
+    property public final androidx.paging.LoadState loadState;
+  }
+
+  public final class NullPaddedListDiffHelperKt {
+  }
+
+  @Deprecated public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor @Deprecated protected PagedListAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    ctor @Deprecated protected PagedListAdapter(androidx.recyclerview.widget.AsyncDifferConfig<T> config);
+    method @Deprecated public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public androidx.paging.PagedList<T>? getCurrentList();
+    method @Deprecated protected T? getItem(int position);
+    method @Deprecated public int getItemCount();
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? currentList);
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? previousList, androidx.paging.PagedList<T>? currentList);
+    method @Deprecated public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList, Runnable? commitCallback);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateFooter(androidx.paging.LoadStateAdapter<?> footer);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeader(androidx.paging.LoadStateAdapter<?> header);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeaderAndFooter(androidx.paging.LoadStateAdapter<?> header, androidx.paging.LoadStateAdapter<?> footer);
+    property public androidx.paging.PagedList<T>? currentList;
+  }
+
+  public abstract class PagingDataAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher, optional kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method protected final T? getItem(@IntRange(from=0) int position);
+    method public int getItemCount();
+    method public final long getItemId(int position);
+    method public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public final T? peek(@IntRange(from=0) int index);
+    method public final void refresh();
+    method public final void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void retry();
+    method public final void setHasStableIds(boolean hasStableIds);
+    method public final androidx.paging.ItemSnapshotList<T> snapshot();
+    method public final suspend Object? submitData(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public final void submitData(androidx.lifecycle.Lifecycle lifecycle, androidx.paging.PagingData<T> pagingData);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateFooter(androidx.paging.LoadStateAdapter<?> footer);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeader(androidx.paging.LoadStateAdapter<?> header);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeaderAndFooter(androidx.paging.LoadStateAdapter<?> header, androidx.paging.LoadStateAdapter<?> footer);
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+  }
+
+  public final class PagingLiveData {
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, androidx.lifecycle.Lifecycle lifecycle);
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, androidx.lifecycle.ViewModel viewModel);
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagingData<Value>> getLiveData(androidx.paging.Pager<Key,Value>);
+  }
+
+}
+
diff --git a/paging/runtime/api/res-3.0.0-beta03.txt b/paging/runtime/api/res-3.0.0-beta03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/runtime/api/res-3.0.0-beta03.txt
diff --git a/paging/runtime/api/restricted_3.0.0-beta03.txt b/paging/runtime/api/restricted_3.0.0-beta03.txt
new file mode 100644
index 0000000..b886d2e
--- /dev/null
+++ b/paging/runtime/api/restricted_3.0.0-beta03.txt
@@ -0,0 +1,131 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  @Deprecated public class AsyncPagedListDiffer<T> {
+    ctor @Deprecated public AsyncPagedListDiffer(androidx.recyclerview.widget.RecyclerView.Adapter<?> adapter, androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    ctor @Deprecated public AsyncPagedListDiffer(androidx.recyclerview.widget.ListUpdateCallback listUpdateCallback, androidx.recyclerview.widget.AsyncDifferConfig<T> config);
+    method @Deprecated public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void addPagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
+    method @Deprecated public final void addPagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>,? super androidx.paging.PagedList<T>,kotlin.Unit> callback);
+    method @Deprecated public androidx.paging.PagedList<T>? getCurrentList();
+    method @Deprecated public T? getItem(int index);
+    method @Deprecated public int getItemCount();
+    method @Deprecated public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void removePagedListListener(androidx.paging.AsyncPagedListDiffer.PagedListListener<T> listener);
+    method @Deprecated public final void removePagedListListener(kotlin.jvm.functions.Function2<? super androidx.paging.PagedList<T>,? super androidx.paging.PagedList<T>,kotlin.Unit> callback);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList, Runnable? commitCallback);
+    property public androidx.paging.PagedList<T>? currentList;
+    property public int itemCount;
+  }
+
+  @Deprecated public static interface AsyncPagedListDiffer.PagedListListener<T> {
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? previousList, androidx.paging.PagedList<T>? currentList);
+  }
+
+  public final class AsyncPagingDataDiffer<T> {
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher, optional kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, androidx.recyclerview.widget.ListUpdateCallback updateCallback);
+    method public void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public T? getItem(@IntRange(from=0) int index);
+    method public int getItemCount();
+    method public kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public T? peek(@IntRange(from=0) int index);
+    method public void refresh();
+    method public void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public void retry();
+    method public androidx.paging.ItemSnapshotList<T> snapshot();
+    method public suspend Object? submitData(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public void submitData(androidx.lifecycle.Lifecycle lifecycle, androidx.paging.PagingData<T> pagingData);
+    property public final int itemCount;
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+  }
+
+  @Deprecated public final class LivePagedListBuilder<Key, Value> {
+    ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public LivePagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    ctor @Deprecated public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public LivePagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    method @Deprecated public androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> build();
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setCoroutineScope(kotlinx.coroutines.CoroutineScope coroutineScope);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public androidx.paging.LivePagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+  }
+
+  public final class LivePagedListKt {
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional java.util.concurrent.Executor fetchExecutor);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional kotlinx.coroutines.CoroutineScope coroutineScope, optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+    method @Deprecated public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagedList<Value>> toLiveData(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional kotlinx.coroutines.CoroutineScope coroutineScope, optional kotlinx.coroutines.CoroutineDispatcher fetchDispatcher);
+  }
+
+  public abstract class LoadStateAdapter<VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor public LoadStateAdapter();
+    method public boolean displayLoadStateAsItem(androidx.paging.LoadState loadState);
+    method public final int getItemCount();
+    method public final int getItemViewType(int position);
+    method public final androidx.paging.LoadState getLoadState();
+    method public int getStateViewType(androidx.paging.LoadState loadState);
+    method public final void onBindViewHolder(VH holder, int position);
+    method public abstract void onBindViewHolder(VH holder, androidx.paging.LoadState loadState);
+    method public final VH onCreateViewHolder(android.view.ViewGroup parent, int viewType);
+    method public abstract VH onCreateViewHolder(android.view.ViewGroup parent, androidx.paging.LoadState loadState);
+    method public final void setLoadState(androidx.paging.LoadState loadState);
+    property public final androidx.paging.LoadState loadState;
+  }
+
+  public final class NullPaddedListDiffHelperKt {
+  }
+
+  @Deprecated public abstract class PagedListAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor @Deprecated protected PagedListAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    ctor @Deprecated protected PagedListAdapter(androidx.recyclerview.widget.AsyncDifferConfig<T> config);
+    method @Deprecated public void addLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public androidx.paging.PagedList<T>? getCurrentList();
+    method @Deprecated protected T? getItem(int position);
+    method @Deprecated public int getItemCount();
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? currentList);
+    method @Deprecated public void onCurrentListChanged(androidx.paging.PagedList<T>? previousList, androidx.paging.PagedList<T>? currentList);
+    method @Deprecated public void removeLoadStateListener(kotlin.jvm.functions.Function2<? super androidx.paging.LoadType,? super androidx.paging.LoadState,kotlin.Unit> listener);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList);
+    method @Deprecated public void submitList(androidx.paging.PagedList<T>? pagedList, Runnable? commitCallback);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateFooter(androidx.paging.LoadStateAdapter<?> footer);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeader(androidx.paging.LoadStateAdapter<?> header);
+    method @Deprecated public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeaderAndFooter(androidx.paging.LoadStateAdapter<?> header, androidx.paging.LoadStateAdapter<?> footer);
+    property public androidx.paging.PagedList<T>? currentList;
+  }
+
+  public abstract class PagingDataAdapter<T, VH extends androidx.recyclerview.widget.RecyclerView.ViewHolder> extends androidx.recyclerview.widget.RecyclerView.Adapter<VH> {
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher, optional kotlinx.coroutines.CoroutineDispatcher workerDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback, optional kotlinx.coroutines.CoroutineDispatcher mainDispatcher);
+    ctor public PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T> diffCallback);
+    method public final void addLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method protected final T? getItem(@IntRange(from=0) int position);
+    method public int getItemCount();
+    method public final long getItemId(int position);
+    method public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> getLoadStateFlow();
+    method public final T? peek(@IntRange(from=0) int index);
+    method public final void refresh();
+    method public final void removeLoadStateListener(kotlin.jvm.functions.Function1<? super androidx.paging.CombinedLoadStates,kotlin.Unit> listener);
+    method public final void retry();
+    method public final void setHasStableIds(boolean hasStableIds);
+    method public final androidx.paging.ItemSnapshotList<T> snapshot();
+    method public final suspend Object? submitData(androidx.paging.PagingData<T> pagingData, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
+    method public final void submitData(androidx.lifecycle.Lifecycle lifecycle, androidx.paging.PagingData<T> pagingData);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateFooter(androidx.paging.LoadStateAdapter<?> footer);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeader(androidx.paging.LoadStateAdapter<?> header);
+    method public final androidx.recyclerview.widget.ConcatAdapter withLoadStateHeaderAndFooter(androidx.paging.LoadStateAdapter<?> header, androidx.paging.LoadStateAdapter<?> footer);
+    property public final kotlinx.coroutines.flow.Flow<androidx.paging.CombinedLoadStates> loadStateFlow;
+  }
+
+  public final class PagingLiveData {
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, androidx.lifecycle.Lifecycle lifecycle);
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, androidx.lifecycle.ViewModel viewModel);
+    method public static <T> androidx.lifecycle.LiveData<androidx.paging.PagingData<T>> cachedIn(androidx.lifecycle.LiveData<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method public static <Key, Value> androidx.lifecycle.LiveData<androidx.paging.PagingData<Value>> getLiveData(androidx.paging.Pager<Key,Value>);
+  }
+
+}
+
diff --git a/paging/runtime/build.gradle b/paging/runtime/build.gradle
index 9c903a7..1396a1a6 100644
--- a/paging/runtime/build.gradle
+++ b/paging/runtime/build.gradle
@@ -40,7 +40,7 @@
     api("androidx.lifecycle:lifecycle-livedata-ktx:2.2.0")
     api("androidx.lifecycle:lifecycle-runtime-ktx:2.2.0")
     api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0")
-    api("androidx.recyclerview:recyclerview:1.2.0-beta01")
+    api("androidx.recyclerview:recyclerview:1.2.0-beta02")
     api(KOTLIN_STDLIB)
     api(KOTLIN_COROUTINES_ANDROID)
     implementation("androidx.core:core-ktx:1.2.0")
diff --git a/paging/runtime/ktx/api/3.0.0-beta03.txt b/paging/runtime/ktx/api/3.0.0-beta03.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/runtime/ktx/api/3.0.0-beta03.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/runtime/ktx/api/public_plus_experimental_3.0.0-beta03.txt b/paging/runtime/ktx/api/public_plus_experimental_3.0.0-beta03.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/runtime/ktx/api/public_plus_experimental_3.0.0-beta03.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/runtime/ktx/api/res-3.0.0-beta03.txt b/paging/runtime/ktx/api/res-3.0.0-beta03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/runtime/ktx/api/res-3.0.0-beta03.txt
diff --git a/paging/runtime/ktx/api/restricted_3.0.0-beta03.txt b/paging/runtime/ktx/api/restricted_3.0.0-beta03.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/runtime/ktx/api/restricted_3.0.0-beta03.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt
index 636a30c..077c81e 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt
@@ -19,6 +19,7 @@
 package androidx.paging
 
 import androidx.arch.core.executor.ArchTaskExecutor
+import androidx.paging.DiffingChangePayload.PLACEHOLDER_POSITION_CHANGE
 import androidx.paging.ListUpdateCallbackFake.OnChangedEvent
 import androidx.paging.ListUpdateCallbackFake.OnInsertedEvent
 import androidx.paging.ListUpdateCallbackFake.OnRemovedEvent
@@ -217,8 +218,8 @@
                 trailingNulls = 2,
                 items = arrayOf("a", "b")
             ),
-            OnInsertedEvent(2, 2),
-            OnInsertedEvent(0, 3)
+            OnInsertedEvent(0, 3),
+            OnInsertedEvent(5, 2)
         )
         // remove some nulls from both ends
         submitAndAssert(
@@ -227,8 +228,10 @@
                 trailingNulls = 1,
                 items = arrayOf("a", "b")
             ),
-            OnRemovedEvent(6, 1),
-            OnRemovedEvent(0, 2)
+            OnRemovedEvent(0, 2),
+            OnChangedEvent(0, 1, PLACEHOLDER_POSITION_CHANGE),
+            OnRemovedEvent(4, 1),
+            OnChangedEvent(3, 1, PLACEHOLDER_POSITION_CHANGE),
         )
         // add to leading, remove from trailing
         submitAndAssert(
@@ -237,8 +240,9 @@
                 trailingNulls = 0,
                 items = arrayOf("a", "b")
             ),
-            OnRemovedEvent(3, 1),
-            OnInsertedEvent(0, 4)
+            OnChangedEvent(0, 1, PLACEHOLDER_POSITION_CHANGE),
+            OnInsertedEvent(0, 4),
+            OnRemovedEvent(7, 1)
         )
         // add trailing, remove from leading
         submitAndAssert(
@@ -247,8 +251,9 @@
                 trailingNulls = 3,
                 items = arrayOf("a", "b")
             ),
-            OnInsertedEvent(7, 3),
-            OnRemovedEvent(0, 4)
+            OnRemovedEvent(0, 4),
+            OnChangedEvent(0, 1, PLACEHOLDER_POSITION_CHANGE),
+            OnInsertedEvent(3, 3)
         )
         assertThat(differ.itemCount).isEqualTo(callback.itemCountFromEvents())
     }
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
index 6f89fd1..69c49c0 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagingDataDifferTest.kt
@@ -17,6 +17,7 @@
 package androidx.paging
 
 import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.paging.DiffingChangePayload.ITEM_TO_PLACEHOLDER
 import androidx.paging.ListUpdateEvent.Changed
 import androidx.paging.ListUpdateEvent.Inserted
 import androidx.paging.ListUpdateEvent.Moved
@@ -250,12 +251,16 @@
             currentPagedSource!!.invalidate()
             advanceUntilIdle()
 
+            // TODO every change event here should have payload and we should also not dispatch
+            //  events with 0 count
+            //  b/182510751
             val expected = listOf(
                 Inserted(0, 100), // [(50 placeholders), 50, 51, (48 placeholders)]
                 Changed(52, 1, null), // [(50 placeholders), 50, 51, 52, (47 placeholders)]
                 Inserted(53, 0), // ignored
-                Inserted(0, 1), // [(51 placeholders), 50, 51, 52, (47 placeholders)]
-                Removed(51, 1), // [(51 placeholders), 51, 52, (47 placeholders)]
+                // refresh
+                Changed(50, 1, ITEM_TO_PLACEHOLDER), // 50 got unloaded
+                // fix prefetch, 50 got reloaded
                 Changed(50, 1, null), // [(50 placeholders), 50, 51, 52, (47 placeholders)]
                 Inserted(0, 0) // ignored
             )
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListTest.kt
index 7fd6143..8a8be14 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListTest.kt
@@ -18,18 +18,23 @@
 
 import android.view.View
 import android.view.ViewGroup
+import androidx.arch.core.executor.ArchTaskExecutor
 import androidx.arch.core.executor.testing.InstantTaskExecutorRule
 import androidx.recyclerview.widget.DiffUtil
 import androidx.recyclerview.widget.RecyclerView
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import androidx.testutils.TestDispatcher
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
-import org.junit.Assert.assertEquals
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.asCoroutineDispatcher
 import org.junit.Assert.assertNotNull
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
@@ -40,6 +45,36 @@
     val instantTaskExecutorRule = InstantTaskExecutorRule()
 
     @Test
+    fun instantiatesPagingSourceOnFetchDispatcher() {
+        var pagingSourcesCreated = 0
+        val pagingSourceFactory = {
+            pagingSourcesCreated++
+            TestPagingSource()
+        }
+        val testDispatcher = TestDispatcher()
+        val livePagedList = LivePagedList(
+            coroutineScope = GlobalScope,
+            initialKey = null,
+            config = PagedList.Config.Builder().setPageSize(10).build(),
+            boundaryCallback = null,
+            pagingSourceFactory = pagingSourceFactory,
+            notifyDispatcher = ArchTaskExecutor.getMainThreadExecutor().asCoroutineDispatcher(),
+            fetchDispatcher = testDispatcher,
+        )
+
+        assertTrue { testDispatcher.queue.isEmpty() }
+        assertEquals(0, pagingSourcesCreated)
+
+        livePagedList.observeForever { }
+
+        assertTrue { testDispatcher.queue.isNotEmpty() }
+        assertEquals(0, pagingSourcesCreated)
+
+        testDispatcher.executeAll()
+        assertEquals(1, pagingSourcesCreated)
+    }
+
+    @Test
     fun toLiveData_dataSourceConfig() {
         val livePagedList = dataSourceFactory.toLiveData(config)
         livePagedList.observeForever {}
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/NullPaddedListDiffHelperTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/NullPaddedListDiffHelperTest.kt
index c119310..8440fa3 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/NullPaddedListDiffHelperTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/NullPaddedListDiffHelperTest.kt
@@ -16,6 +16,9 @@
 
 package androidx.paging
 
+import androidx.paging.DiffingChangePayload.ITEM_TO_PLACEHOLDER
+import androidx.paging.DiffingChangePayload.PLACEHOLDER_POSITION_CHANGE
+import androidx.paging.DiffingChangePayload.PLACEHOLDER_TO_ITEM
 import androidx.paging.ListUpdateCallbackFake.OnChangedEvent
 import androidx.paging.ListUpdateCallbackFake.OnInsertedEvent
 import androidx.paging.ListUpdateCallbackFake.OnMovedEvent
@@ -23,6 +26,7 @@
 import androidx.recyclerview.widget.DiffUtil
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -58,10 +62,25 @@
             Storage(5, listOf("a", "b"), 5),
             Storage(5, listOf("a", "b", "c"), 4)
         ) {
-            assertEquals(OnRemovedEvent(11, 1), it.onRemovedEvents[0])
-            assertEquals(OnInsertedEvent(7, 1), it.onInsertedEvents[0])
-            // NOTE: ideally would be onChanged(7, 1, null)
-            assertEquals(2, it.interactions)
+            assertEquals(
+                OnChangedEvent(7, 1, PLACEHOLDER_TO_ITEM),
+                it.onChangedEvents[0]
+            )
+            assertEquals(1, it.interactions)
+        }
+    }
+
+    @Test
+    fun appendUnload() {
+        validateTwoListDiff(
+            Storage(5, listOf("a", "b", "c"), 4),
+            Storage(5, listOf("a", "b"), 5),
+        ) {
+            assertEquals(
+                OnChangedEvent(7, 1, ITEM_TO_PLACEHOLDER),
+                it.onChangedEvents[0]
+            )
+            assertEquals(1, it.interactions)
         }
     }
 
@@ -71,10 +90,25 @@
             Storage(5, listOf("b", "c"), 5),
             Storage(4, listOf("a", "b", "c"), 5)
         ) {
-            assertEquals(OnRemovedEvent(0, 1), it.onRemovedEvents[0])
-            assertEquals(OnInsertedEvent(4, 1), it.onInsertedEvents[0])
-            // NOTE: ideally would be onChanged(4, 1, null);
-            assertEquals(2, it.interactions)
+            assertEquals(
+                OnChangedEvent(4, 1, PLACEHOLDER_TO_ITEM),
+                it.onChangedEvents[0]
+            )
+            assertEquals(1, it.interactions)
+        }
+    }
+
+    @Test
+    fun prependUnload() {
+        validateTwoListDiff(
+            Storage(4, listOf("a", "b", "c"), 5),
+            Storage(5, listOf("b", "c"), 5),
+        ) {
+            assertEquals(
+                OnChangedEvent(4, 1, ITEM_TO_PLACEHOLDER),
+                it.onChangedEvents[0]
+            )
+            assertEquals(1, it.interactions)
         }
     }
 
@@ -103,6 +137,88 @@
     }
 
     @Test
+    fun moveBeforePlaceholders() {
+        validateTwoListDiff(
+            Storage(5, listOf("a", "b", "c"), 5),
+            Storage(5, listOf("c", "x", "y", "a", "b"), 5)
+        ) {
+            assertThat(
+                it.allEvents
+            ).containsExactly(
+                // convert 2 placeholders to x,y
+                OnChangedEvent(3, 2, PLACEHOLDER_TO_ITEM),
+                // move c to before x,y
+                OnMovedEvent(7, 3),
+                // these placeholders will shift down in the list as we'll re-add 2 placeholders
+                OnChangedEvent(0, 3, PLACEHOLDER_POSITION_CHANGE),
+                // now we need 2 new placeholders
+                OnInsertedEvent(0, 2),
+                // all trailing placeholders shifted 2 positions
+                OnChangedEvent(10, 5, PLACEHOLDER_POSITION_CHANGE),
+            )
+        }
+    }
+
+    @Test
+    fun moveBeforePlaceholders_noPlaceholderShift() {
+        validateTwoListDiff(
+            Storage(5, listOf("a", "b", "c"), 5),
+            Storage(3, listOf("c", "x", "y", "a", "b"), 5)
+        ) {
+            assertThat(
+                it.allEvents
+            ).containsExactly(
+                OnChangedEvent(3, 2, PLACEHOLDER_TO_ITEM),
+                OnMovedEvent(7, 3)
+            )
+        }
+    }
+
+    @Test
+    fun moveAfterPlaceholders() {
+        validateTwoListDiff(
+            Storage(5, listOf("a", "b", "c"), 5),
+            Storage(5, listOf("b", "c", "x", "y", "a"), 5)
+        ) {
+            assertThat(
+                it.allEvents
+            ).containsExactly(
+                // insert x, y as placeholder changes
+                OnChangedEvent(8, 2, PLACEHOLDER_TO_ITEM),
+                // move a to after x,y
+                OnMovedEvent(5, 9),
+                // insert new placeholders to the end
+                OnInsertedEvent(13, 2)
+            )
+        }
+    }
+
+    @Test
+    fun loadedMore_withMorePlaceholdersAfter() {
+        validateTwoListDiff(
+            Storage(4, listOf("a", "b", "c", "d", "e"), 1),
+            Storage(1, listOf("d", "e", "f", "g"), 20)
+        ) {
+            assertThat(
+                it.allEvents
+            ).containsExactly(
+                // add f to replace the placeholder after e
+                OnChangedEvent(9, 1, PLACEHOLDER_TO_ITEM),
+                // add g, we don't have a placeholder for it so it is an insertion
+                OnInsertedEvent(10, 1),
+                // rm a,b,c
+                OnRemovedEvent(4, 3),
+                // rm 3 unnecessary leading placeholders
+                OnRemovedEvent(0, 3),
+                // 3rd placeholder moved to pos 0, so dispatch a change for it
+                OnChangedEvent(0, 1, PLACEHOLDER_POSITION_CHANGE),
+                // add 20 trailing placeholders
+                OnInsertedEvent(5, 20)
+            )
+        }
+    }
+
+    @Test
     fun transformAnchorIndex_removal() {
         validateTwoListDiffTransform(
             Storage(5, listOf("a", "b", "c", "d", "e"), 5),
@@ -207,6 +323,44 @@
         }
     }
 
+    @Test
+    fun distinct_jumpToTop() {
+        validateTwoListDiff(
+            Storage(4, listOf("c_4", "4", "d_5", "5", "e_6", "6"), 3),
+            Storage(0, listOf("a_0", "0", "b_1", "1"), 8)
+        ) {
+            assertThat(it.allEvents).containsExactly(
+                // replace previous items with placeholders
+                OnChangedEvent(4, 6, ITEM_TO_PLACEHOLDER),
+                // swap first 4 placeholders with newly loaded items
+                OnChangedEvent(0, 4, PLACEHOLDER_TO_ITEM),
+                // remove extra placeholder
+                OnRemovedEvent(12, 1)
+            ).inOrder()
+        }
+    }
+
+    @Test
+    fun distinct_jumpToBottom() {
+        validateTwoListDiff(
+            Storage(4, listOf("a_4", "4", "b_5", "5", "c_6", "6"), 3),
+            Storage(8, listOf("d_8", "8", "e_9", "9"), 0),
+        ) {
+            assertThat(it.allEvents).containsExactly(
+                // remove c_6 and 6, their positions overlap w/ newly loaded items
+                OnRemovedEvent(8, 2),
+                // now insert d_8 and 8
+                OnInsertedEvent(8, 2),
+                // first 4 of the loaded items becomes placeholders: "a_4", "4", "b_5", "5"
+                OnChangedEvent(4, 4, ITEM_TO_PLACEHOLDER),
+                // insert e_9 and 9 using placeholders
+                OnChangedEvent(10, 2, PLACEHOLDER_TO_ITEM),
+                // finally, remove the last placeholder that we won't use
+                OnRemovedEvent(12, 1)
+            )
+        }
+    }
+
     companion object {
         private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<String>() {
             override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/NullPaddedListDiffWithRecyclerViewTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/NullPaddedListDiffWithRecyclerViewTest.kt
new file mode 100644
index 0000000..a1093bf
--- /dev/null
+++ b/paging/runtime/src/androidTest/java/androidx/paging/NullPaddedListDiffWithRecyclerViewTest.kt
@@ -0,0 +1,926 @@
+/*
+ * 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.paging
+
+import android.content.Context
+import android.view.View
+import android.view.View.MeasureSpec.EXACTLY
+import android.view.ViewGroup
+import androidx.recyclerview.widget.AdapterListUpdateCallback
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.ListUpdateCallback
+import androidx.recyclerview.widget.RecyclerView
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.random.Random
+
+/**
+ * For some tests, this test uses a real recyclerview with a real adapter to serve as an
+ * integration test so that we can validate all updates and state restorations after updates.
+ */
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class NullPaddedListDiffWithRecyclerViewTest {
+    private lateinit var context: Context
+    private lateinit var recyclerView: RecyclerView
+    private lateinit var adapter: NullPaddedListAdapter
+
+    @Before
+    fun init() {
+        context = ApplicationProvider.getApplicationContext()
+        recyclerView = RecyclerView(
+            context
+        ).also {
+            it.layoutManager = LinearLayoutManager(context)
+            it.itemAnimator = null
+        }
+        adapter = NullPaddedListAdapter()
+        recyclerView.adapter = adapter
+    }
+
+    // this is no different that init but reads better in tests to have a reset method
+    private fun reset() {
+        init()
+    }
+
+    private fun measureAndLayout() {
+        recyclerView.measure(EXACTLY or 100, EXACTLY or RV_HEIGHT)
+        recyclerView.layout(0, 0, 100, RV_HEIGHT)
+    }
+
+    @Test
+    fun basic() {
+        val storage = NullPaddedStorage(
+            placeholdersBefore = 0,
+            data = createItems(0, 10),
+            placeholdersAfter = 0
+        )
+        adapter.setItems(storage)
+        measureAndLayout()
+        val snapshot = captureUISnapshot()
+        assertThat(snapshot).containsExactlyElementsIn(
+            createExpectedSnapshot(
+                firstItemTopOffset = 0,
+                startItemIndex = 0,
+                backingList = storage
+            )
+        )
+    }
+
+    @Test
+    fun distinctLists_fullyOverlappingRange() {
+        val pre = NullPaddedStorage(
+            placeholdersBefore = 10,
+            data = createItems(startId = 10, count = 8),
+            placeholdersAfter = 30
+        )
+        val post = NullPaddedStorage(
+            placeholdersBefore = 10,
+            data = createItems(startId = 100, count = 8),
+            placeholdersAfter = 30
+        )
+        distinctListTest_withVariousInitialPositions(
+            pre = pre,
+            post = post,
+        )
+    }
+
+    @Test
+    fun distinctLists_loadedBefore_or_After() {
+        val pre = NullPaddedStorage(
+            placeholdersBefore = 10,
+            data = createItems(startId = 10, count = 10),
+            placeholdersAfter = 10
+        )
+        val post = NullPaddedStorage(
+            placeholdersBefore = 5,
+            data = createItems(startId = 5, count = 5),
+            placeholdersAfter = 20
+        )
+        distinctListTest_withVariousInitialPositions(
+            pre = pre,
+            post = post
+        )
+    }
+
+    @Test
+    fun distinctLists_partiallyOverlapping() {
+        val pre = NullPaddedStorage(
+            placeholdersBefore = 10,
+            data = createItems(startId = 0, count = 8),
+            placeholdersAfter = 30
+        )
+        val post = NullPaddedStorage(
+            placeholdersBefore = 15,
+            data = createItems(startId = 100, count = 8),
+            placeholdersAfter = 30
+        )
+        distinctListTest_withVariousInitialPositions(
+            pre = pre,
+            post = post,
+        )
+    }
+
+    @Test
+    fun distinctLists_fewerItemsLoaded_withMorePlaceholdersBefore() {
+        val pre = NullPaddedStorage(
+            placeholdersBefore = 10,
+            data = createItems(startId = 10, count = 8),
+            placeholdersAfter = 30
+        )
+        val post = NullPaddedStorage(
+            placeholdersBefore = 15,
+            data = createItems(startId = 100, count = 3),
+            placeholdersAfter = 30
+        )
+        distinctListTest_withVariousInitialPositions(
+            pre = pre,
+            post = post,
+        )
+    }
+
+    @Test
+    fun distinctLists_noPlaceholdersLeft() {
+        val pre = NullPaddedStorage(
+            placeholdersBefore = 10,
+            data = createItems(startId = 10, count = 8),
+            placeholdersAfter = 30
+        )
+        val post = NullPaddedStorage(
+            placeholdersBefore = 0,
+            data = createItems(startId = 100, count = 3),
+            placeholdersAfter = 0
+        )
+        distinctListTest_withVariousInitialPositions(
+            pre = pre,
+            post = post,
+        )
+    }
+
+    @Test
+    fun distinctLists_moreItemsLoaded() {
+        val pre = NullPaddedStorage(
+            placeholdersBefore = 10,
+            data = createItems(startId = 10, count = 3),
+            placeholdersAfter = 30
+        )
+        val post = NullPaddedStorage(
+            placeholdersBefore = 10,
+            data = createItems(startId = 100, count = 8),
+            placeholdersAfter = 30
+        )
+        distinctListTest_withVariousInitialPositions(
+            pre = pre,
+            post = post,
+        )
+    }
+
+    @Test
+    fun distinctLists_moreItemsLoaded_andAlsoMoreOffset() {
+        val pre = NullPaddedStorage(
+            placeholdersBefore = 10,
+            data = createItems(startId = 10, count = 3),
+            placeholdersAfter = 30
+        )
+        val post = NullPaddedStorage(
+            placeholdersBefore = 15,
+            data = createItems(startId = 100, count = 8),
+            placeholdersAfter = 30
+        )
+        distinctListTest_withVariousInitialPositions(
+            pre = pre,
+            post = post,
+        )
+    }
+
+    @Test
+    fun distinctLists_expandShrink() {
+        val pre = NullPaddedStorage(
+            placeholdersBefore = 10,
+            data = createItems(10, 10),
+            placeholdersAfter = 20
+        )
+        val post = NullPaddedStorage(
+            placeholdersBefore = 0,
+            data = createItems(100, 1),
+            placeholdersAfter = 0
+        )
+        distinctListTest_withVariousInitialPositions(
+            pre = pre,
+            post = post,
+        )
+    }
+
+    /**
+     * Runs a state restoration test with various "current scroll positions".
+     */
+    private fun distinctListTest_withVariousInitialPositions(
+        pre: NullPaddedStorage,
+        post: NullPaddedStorage
+    ) {
+        // try restoring positions in different list states
+        val minSize = minOf(pre.size, post.size)
+        val lastTestablePosition = (minSize - (RV_HEIGHT / ITEM_HEIGHT)).coerceAtLeast(0)
+        (0..lastTestablePosition).forEach { initialPos ->
+            distinctListTest(
+                pre = pre,
+                post = post,
+                initialListPos = initialPos,
+            )
+            reset()
+            distinctListTest(
+                pre = post, // intentional, we are trying to test going in reverse direction
+                post = pre,
+                initialListPos = initialPos,
+            )
+            reset()
+        }
+    }
+
+    @Test
+    fun distinctLists_visibleRangeRemoved() {
+        val pre = NullPaddedStorage(
+            placeholdersBefore = 10,
+            data = createItems(10, 10),
+            placeholdersAfter = 30
+        )
+        val post = NullPaddedStorage(
+            placeholdersBefore = 0,
+            data = createItems(100, 4),
+            placeholdersAfter = 20
+        )
+        swapListTest(
+            pre = pre,
+            post = post,
+            preSwapAction = {
+                recyclerView.scrollBy(0, 30 * ITEM_HEIGHT)
+            },
+            validate = { _, newSnapshot ->
+                assertThat(newSnapshot).containsExactlyElementsIn(
+                    createExpectedSnapshot(
+                        startItemIndex = post.size - RV_HEIGHT / ITEM_HEIGHT,
+                        backingList = post
+                    )
+                )
+            }
+        )
+    }
+
+    @Test
+    fun distinctLists_validateDiff() {
+        val pre = NullPaddedStorage(
+            placeholdersBefore = 10,
+            data = createItems(10, 10), // their positions won't be in the new list
+            placeholdersAfter = 20
+        )
+        val post = NullPaddedStorage(
+            placeholdersBefore = 0,
+            data = createItems(100, 1),
+            placeholdersAfter = 0
+        )
+        updateDiffTest(pre, post)
+    }
+
+    @Test
+    @LargeTest
+    fun random_distinctListTest() {
+        // this is a random test but if it fails, the exception will have enough information to
+        // create an isolated test
+        val rand = Random(System.nanoTime())
+        fun randomNullPaddedStorage(startId: Int) = NullPaddedStorage(
+            placeholdersBefore = rand.nextInt(0, 20),
+            data = createItems(
+                startId = startId,
+                count = rand.nextInt(0, 20)
+            ),
+            placeholdersAfter = rand.nextInt(0, 20)
+        )
+        repeat(RANDOM_TEST_REPEAT_SIZE) {
+            updateDiffTest(
+                pre = randomNullPaddedStorage(0),
+                post = randomNullPaddedStorage(1_000)
+            )
+        }
+    }
+
+    @Test
+    fun continuousMatch_1() {
+        val pre = NullPaddedStorage(
+            placeholdersBefore = 4,
+            data = createItems(startId = 0, count = 16),
+            placeholdersAfter = 1
+        )
+        val post = NullPaddedStorage(
+            placeholdersBefore = 1,
+            data = createItems(startId = 13, count = 4),
+            placeholdersAfter = 19
+        )
+        updateDiffTest(pre, post)
+    }
+
+    @Test
+    fun continuousMatch_2() {
+        val pre = NullPaddedStorage(
+            placeholdersBefore = 6,
+            data = createItems(startId = 0, count = 9),
+            placeholdersAfter = 19
+        )
+        val post = NullPaddedStorage(
+            placeholdersBefore = 14,
+            data = createItems(startId = 4, count = 3),
+            placeholdersAfter = 11
+        )
+        updateDiffTest(pre, post)
+    }
+
+    @Test
+    fun continuousMatch_3() {
+        val pre = NullPaddedStorage(
+            placeholdersBefore = 11,
+            data = createItems(startId = 0, count = 4),
+            placeholdersAfter = 6
+        )
+        val post = NullPaddedStorage(
+            placeholdersBefore = 7,
+            data = createItems(startId = 0, count = 1),
+            placeholdersAfter = 11
+        )
+        updateDiffTest(pre, post)
+    }
+
+    @Test
+    fun continuousMatch_4() {
+        val pre = NullPaddedStorage(
+            placeholdersBefore = 4,
+            data = createItems(startId = 0, count = 15),
+            placeholdersAfter = 18
+        )
+        val post = NullPaddedStorage(
+            placeholdersBefore = 11,
+            data = createItems(startId = 5, count = 17),
+            placeholdersAfter = 9
+        )
+        updateDiffTest(pre, post)
+    }
+
+    @Test
+    @LargeTest
+    fun randomTest_withContinuousMatch() {
+        randomContinuousMatchTest(shuffle = false)
+    }
+
+    @Test
+    @LargeTest
+    fun randomTest_withContinuousMatch_withShuffle() {
+        randomContinuousMatchTest(shuffle = true)
+    }
+
+    /**
+     * Tests that if two lists have some overlaps, we dispatch the right diff events.
+     * It can also optionally shuffle the lists.
+     */
+    private fun randomContinuousMatchTest(shuffle: Boolean) {
+        // this is a random test but if it fails, the exception will have enough information to
+        // create an isolated test
+        val rand = Random(System.nanoTime())
+        fun randomNullPaddedStorage(startId: Int) = NullPaddedStorage(
+            placeholdersBefore = rand.nextInt(0, 20),
+            data = createItems(
+                startId = startId,
+                count = rand.nextInt(0, 20)
+            ).let {
+                if (shuffle) it.shuffled()
+                else it
+            },
+            placeholdersAfter = rand.nextInt(0, 20)
+        )
+        repeat(RANDOM_TEST_REPEAT_SIZE) {
+            val pre = randomNullPaddedStorage(0)
+            val post = randomNullPaddedStorage(
+                startId = if (pre.storageCount > 0) {
+                    pre.getFromStorage(rand.nextInt(pre.storageCount)).id
+                } else {
+                    0
+                }
+            )
+            updateDiffTest(
+                pre = pre,
+                post = post
+            )
+        }
+    }
+
+    /**
+     * Validates that the update events between [pre] and [post] are correct.
+     */
+    private fun updateDiffTest(
+        pre: NullPaddedStorage,
+        post: NullPaddedStorage
+    ) {
+        val callback = ValidatingListUpdateCallback(pre, post)
+        val diffResult = pre.computeDiff(post, NullPaddedListItem.CALLBACK)
+        pre.dispatchDiff(callback, post, diffResult)
+        callback.validateRunningListAgainst()
+    }
+
+    private fun distinctListTest(
+        pre: NullPaddedStorage,
+        post: NullPaddedStorage,
+        initialListPos: Int,
+        finalListPos: Int = initialListPos
+    ) {
+        // try with various initial list positioning.
+        // in every case, we should preserve our position
+        swapListTest(
+            pre = pre,
+            post = post,
+            preSwapAction = {
+                recyclerView.scrollBy(
+                    0,
+                    initialListPos * ITEM_HEIGHT
+                )
+            },
+            validate = { _, snapshot ->
+                assertWithMessage(
+                    """
+                    initial pos: $initialListPos
+                    expected final pos: $finalListPos
+                    pre: $pre
+                    post: $post
+                    """.trimIndent()
+                ).that(snapshot).containsExactlyElementsIn(
+                    createExpectedSnapshot(
+                        startItemIndex = finalListPos,
+                        backingList = post
+                    )
+                )
+            }
+        )
+    }
+
+    /**
+     * Helper function to run tests where we submit the [pre] list, run [preSwapAction] (where it
+     * can scroll etc) then submit [post] list, run [postSwapAction] and then call [validate]
+     * with UI snapshots.
+     */
+    private fun swapListTest(
+        pre: NullPaddedStorage,
+        post: NullPaddedStorage,
+        preSwapAction: () -> Unit = {},
+        postSwapAction: () -> Unit = {},
+        validate: (preCapture: List<UIItemSnapshot>, postCapture: List<UIItemSnapshot>) -> Unit
+    ) {
+        adapter.setItems(pre)
+        measureAndLayout()
+        preSwapAction()
+        val preSnapshot = captureUISnapshot()
+        adapter.setItems(post)
+        postSwapAction()
+        measureAndLayout()
+        val postSnapshot = captureUISnapshot()
+        validate(preSnapshot, postSnapshot)
+    }
+
+    /**
+     * Captures positions and data of each visible item in the RecyclerView.
+     */
+    private fun captureUISnapshot(): List<UIItemSnapshot> {
+        return (0 until recyclerView.childCount).mapNotNull { childPos ->
+            val view = recyclerView.getChildAt(childPos)!!
+            if (view.top < RV_HEIGHT && view.bottom > 0) {
+                val viewHolder = recyclerView.getChildViewHolder(view) as NullPaddedListViewHolder
+                UIItemSnapshot(
+                    top = view.top,
+                    boundItem = viewHolder.boundItem,
+                    boundPos = viewHolder.boundPos
+                )
+            } else {
+                null
+            }
+        }
+    }
+
+    /**
+     * Custom adapter class that also validates its update events to ensure they are correct.
+     */
+    private class NullPaddedListAdapter : RecyclerView.Adapter<NullPaddedListViewHolder>() {
+        private var items: NullPaddedList<NullPaddedListItem>? = null
+
+        fun setItems(items: NullPaddedList<NullPaddedListItem>) {
+            val previousItems = this.items
+            val myItems = this.items
+            if (myItems == null) {
+                notifyItemRangeInserted(0, items.size)
+            } else {
+                val diff = myItems.computeDiff(items, NullPaddedListItem.CALLBACK)
+                val diffObserver = TrackingAdapterObserver(previousItems, items)
+                registerAdapterDataObserver(diffObserver)
+                val callback = AdapterListUpdateCallback(this)
+                myItems.dispatchDiff(callback, items, diff)
+                unregisterAdapterDataObserver(diffObserver)
+                diffObserver.validateRunningListAgainst()
+            }
+            this.items = items
+        }
+
+        override fun onCreateViewHolder(
+            parent: ViewGroup,
+            viewType: Int
+        ): NullPaddedListViewHolder {
+            return NullPaddedListViewHolder(parent.context).also {
+                it.itemView.layoutParams = RecyclerView.LayoutParams(
+                    RecyclerView.LayoutParams.MATCH_PARENT,
+                    ITEM_HEIGHT
+                )
+            }
+        }
+
+        override fun onBindViewHolder(holder: NullPaddedListViewHolder, position: Int) {
+            val item = items?.get(position)
+            holder.boundItem = item
+            holder.boundPos = position
+        }
+
+        override fun getItemCount(): Int {
+            return items?.size ?: 0
+        }
+    }
+
+    private data class NullPaddedListItem(
+        val id: Int,
+        val value: String
+    ) {
+        companion object {
+            val CALLBACK = object : DiffUtil.ItemCallback<NullPaddedListItem>() {
+                override fun areItemsTheSame(
+                    oldItem: NullPaddedListItem,
+                    newItem: NullPaddedListItem
+                ): Boolean {
+                    return oldItem.id == newItem.id
+                }
+
+                override fun areContentsTheSame(
+                    oldItem: NullPaddedListItem,
+                    newItem: NullPaddedListItem
+                ): Boolean {
+                    return oldItem == newItem
+                }
+            }
+        }
+    }
+
+    private class NullPaddedListViewHolder(
+        context: Context
+    ) : RecyclerView.ViewHolder(View(context)) {
+        var boundItem: NullPaddedListItem? = null
+        var boundPos: Int = -1
+        override fun toString(): String {
+            return "VH[$boundPos , $boundItem]"
+        }
+    }
+
+    private data class UIItemSnapshot(
+        // top coordinate of the item
+        val top: Int,
+        // the item it is bound to, unless it was a placeholder
+        val boundItem: NullPaddedListItem?,
+        // the position it was bound to
+        val boundPos: Int
+    )
+
+    private class NullPaddedStorage(
+        override val placeholdersBefore: Int,
+        private val data: List<NullPaddedListItem>,
+        override val placeholdersAfter: Int
+    ) : NullPaddedList<NullPaddedListItem> {
+        private val stringRepresentation by lazy {
+            """
+            $placeholdersBefore:${data.size}:$placeholdersAfter
+            $data
+            """.trimIndent()
+        }
+
+        override fun getFromStorage(localIndex: Int): NullPaddedListItem = data[localIndex]
+
+        override val size: Int
+            get() = placeholdersBefore + data.size + placeholdersAfter
+
+        override val storageCount: Int
+            get() = data.size
+
+        override fun toString() = stringRepresentation
+    }
+
+    private fun createItems(
+        startId: Int,
+        count: Int
+    ): List<NullPaddedListItem> {
+        return (startId until startId + count).map {
+            NullPaddedListItem(
+                id = it,
+                value = "$it"
+            )
+        }
+    }
+
+    /**
+     * Creates an expected UI snapshot based on the given list and scroll position / offset.
+     */
+    private fun createExpectedSnapshot(
+        firstItemTopOffset: Int = 0,
+        startItemIndex: Int,
+        backingList: NullPaddedList<NullPaddedListItem>
+    ): List<UIItemSnapshot> {
+        check(firstItemTopOffset <= 0) {
+            "first item offset should not be negative"
+        }
+        var remainingHeight = RV_HEIGHT - firstItemTopOffset
+        var pos = startItemIndex
+        var top = firstItemTopOffset
+        val result = mutableListOf<UIItemSnapshot>()
+        while (remainingHeight > 0 && pos < backingList.size) {
+            result.add(
+                UIItemSnapshot(
+                    top = top,
+                    boundItem = backingList.get(pos),
+                    boundPos = pos
+                )
+            )
+            top += ITEM_HEIGHT
+            remainingHeight -= ITEM_HEIGHT
+            pos++
+        }
+        return result
+    }
+
+    /**
+     * A ListUpdateCallback implementation that tracks all change notifications and then validate
+     * that
+     * a) changes are correct
+     * b) no unnecessary events are dispatched (e.g. dispatching change for an item then removing
+     * it)
+     */
+    private class ValidatingListUpdateCallback<T>(
+        previousList: NullPaddedList<T>?,
+        private val newList: NullPaddedList<T>
+    ) : ListUpdateCallback {
+        // used in assertion messages
+        val msg = """
+                oldList: $previousList
+                newList: $newList
+        """.trimIndent()
+
+        // all changes are applied to this list, at the end, we'll validate against the new list
+        // to ensure all updates made sense and no unnecessary updates are made
+        val runningList: MutableList<ListSnapshotItem> =
+            previousList?.createSnapshot() ?: mutableListOf()
+
+        private val size: Int
+            get() = runningList.size
+
+        private fun Int.assertWithinBounds() {
+            assertWithMessage(msg).that(this).isAtLeast(0)
+            assertWithMessage(msg).that(this).isAtMost(size)
+        }
+
+        override fun onInserted(position: Int, count: Int) {
+            position.assertWithinBounds()
+            assertWithMessage(msg).that(count).isAtLeast(1)
+            repeat(count) {
+                runningList.add(position, ListSnapshotItem.Inserted)
+            }
+        }
+
+        override fun onRemoved(position: Int, count: Int) {
+            position.assertWithinBounds()
+            (position + count).assertWithinBounds()
+            assertWithMessage(msg).that(count).isAtLeast(1)
+            (position until position + count).forEach { pos ->
+                assertWithMessage(
+                    "$msg\nshouldn't be removing an item that already got a change event" +
+                        " pos: $pos , ${runningList[pos]}"
+                )
+                    .that(runningList[pos].isOriginalItem())
+                    .isTrue()
+            }
+            repeat(count) {
+                runningList.removeAt(position)
+            }
+        }
+
+        override fun onMoved(fromPosition: Int, toPosition: Int) {
+            fromPosition.assertWithinBounds()
+            toPosition.assertWithinBounds()
+            runningList.add(toPosition, runningList.removeAt(fromPosition))
+        }
+
+        override fun onChanged(position: Int, count: Int, payload: Any?) {
+            position.assertWithinBounds()
+            (position + count).assertWithinBounds()
+            assertWithMessage(msg).that(count).isAtLeast(1)
+            (position until position + count).forEach { pos ->
+                // make sure we don't dispatch overlapping updates
+                assertWithMessage(
+                    "$msg\nunnecessary change event for position $pos $payload " +
+                        "${runningList[pos]}"
+                )
+                    .that(runningList[pos].isOriginalItem())
+                    .isTrue()
+                if (payload == DiffingChangePayload.PLACEHOLDER_TO_ITEM ||
+                    payload == DiffingChangePayload.PLACEHOLDER_POSITION_CHANGE
+                ) {
+                    assertWithMessage(msg).that(runningList[pos]).isInstanceOf(
+                        ListSnapshotItem.Placeholder::class.java
+                    )
+                } else {
+                    assertWithMessage(msg).that(runningList[pos]).isInstanceOf(
+                        ListSnapshotItem.Item::class.java
+                    )
+                }
+                runningList[pos] = ListSnapshotItem.Changed(
+                    payload = payload as? DiffingChangePayload
+                )
+            }
+        }
+
+        fun validateRunningListAgainst() {
+            // check for size first
+            assertWithMessage(msg).that(size).isEqualTo(newList.size)
+            val newListSnapshot = newList.createSnapshot()
+            runningList.forEachIndexed { index, listSnapshotItem ->
+                val newListItem = newListSnapshot[index]
+                listSnapshotItem.assertReplacement(
+                    msg,
+                    newListItem
+                )
+                if (!listSnapshotItem.isOriginalItem()) {
+                    // if it changed, replace from new snapshot
+                    runningList[index] = newListSnapshot[index]
+                }
+            }
+            // now after this, each list must be exactly equal, if not, something is wrong
+            assertWithMessage(msg).that(runningList).containsExactlyElementsIn(newListSnapshot)
+        }
+    }
+
+    private class TrackingAdapterObserver<T>(
+        previousList: NullPaddedList<T>?,
+        postList: NullPaddedList<T>
+    ) : RecyclerView.AdapterDataObserver() {
+        private val callback = ValidatingListUpdateCallback(previousList, postList)
+
+        override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
+            callback.onChanged(positionStart, itemCount, null)
+        }
+
+        override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
+            callback.onChanged(positionStart, itemCount, payload)
+        }
+
+        override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
+            callback.onInserted(positionStart, itemCount)
+        }
+
+        override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
+            callback.onRemoved(positionStart, itemCount)
+        }
+
+        fun validateRunningListAgainst() {
+            callback.validateRunningListAgainst()
+        }
+    }
+
+    companion object {
+        private const val RV_HEIGHT = 100
+        private const val ITEM_HEIGHT = 10
+        private const val RANDOM_TEST_REPEAT_SIZE = 1_000
+    }
+}
+
+private fun <T> NullPaddedList<T>.get(index: Int): T? {
+    if (index < placeholdersBefore) return null
+    val storageIndex = index - placeholdersBefore
+    if (storageIndex >= storageCount) return null
+    return getFromStorage(storageIndex)
+}
+
+/**
+ * Create a snapshot of this current that can be used to verify diffs.
+ */
+private fun <T> NullPaddedList<T>.createSnapshot(): MutableList<ListSnapshotItem> = (0 until size)
+    .mapTo(mutableListOf()) { pos ->
+        get(pos)?.let {
+            ListSnapshotItem.Item(it)
+        } ?: ListSnapshotItem.Placeholder(pos)
+    }
+
+/**
+ * Sealed classes to identify items in the list.
+ */
+internal sealed class ListSnapshotItem {
+    // means the item didn't change at all in diffs.
+    fun isOriginalItem() = this is Item<*> || this is Placeholder
+
+    /**
+     * Asserts that this item properly represents the replacement (newListItem).
+     */
+    abstract fun assertReplacement(
+        msg: String,
+        newListItem: ListSnapshotItem
+    )
+
+    data class Item<T>(val item: T) : ListSnapshotItem() {
+        override fun assertReplacement(
+            msg: String,
+            newListItem: ListSnapshotItem
+        ) {
+            // no change
+            assertWithMessage(msg).that(
+                newListItem
+            ).isEqualTo(this)
+        }
+    }
+
+    data class Placeholder(val pos: Int) : ListSnapshotItem() {
+        override fun assertReplacement(
+            msg: String,
+            newListItem: ListSnapshotItem
+        ) {
+            assertWithMessage(msg).that(
+                newListItem
+            ).isInstanceOf(
+                Placeholder::class.java
+            )
+            val replacement = newListItem as Placeholder
+            // make sure position didn't change. If it did, we would be replaced with a [Changed].
+            assertWithMessage(msg).that(
+                pos
+            ).isEqualTo(replacement.pos)
+        }
+    }
+
+    /**
+     * Inserted into the list when we receive a change notification about an item/placeholder.
+     */
+    data class Changed(val payload: DiffingChangePayload?) : ListSnapshotItem() {
+        override fun assertReplacement(
+            msg: String,
+            newListItem: ListSnapshotItem
+        ) {
+            // there are 4 cases for changes.
+            // is either placeholder -> placeholder with new position
+            // placeholder to item
+            // item to placeholder
+            // item change from original diffing.
+            when (payload) {
+                DiffingChangePayload.ITEM_TO_PLACEHOLDER -> {
+                    assertWithMessage(msg).that(newListItem)
+                        .isInstanceOf(Placeholder::class.java)
+                }
+                DiffingChangePayload.PLACEHOLDER_TO_ITEM -> {
+                    assertWithMessage(msg).that(newListItem)
+                        .isInstanceOf(Item::class.java)
+                }
+                DiffingChangePayload.PLACEHOLDER_POSITION_CHANGE -> {
+                    assertWithMessage(msg).that(newListItem)
+                        .isInstanceOf(Placeholder::class.java)
+                }
+                else -> {
+                    // item change that came from diffing.
+                    assertWithMessage(msg).that(newListItem)
+                        .isInstanceOf(Item::class.java)
+                }
+            }
+        }
+    }
+
+    /**
+     * Used when an item/placeholder is inserted to the list
+     */
+    object Inserted : ListSnapshotItem() {
+        override fun assertReplacement(msg: String, newListItem: ListSnapshotItem) {
+            // nothing to assert here, it can represent anything in the new list.
+        }
+    }
+}
\ No newline at end of file
diff --git a/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.kt b/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.kt
index 17d5679..f9ff5b4 100644
--- a/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.kt
+++ b/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.kt
@@ -425,7 +425,7 @@
     internal fun latchPagedList(
         @Suppress("DEPRECATION") newList: PagedList<T>,
         @Suppress("DEPRECATION") diffSnapshot: PagedList<T>,
-        diffResult: DiffUtil.DiffResult,
+        diffResult: NullPaddedDiffResult,
         recordingCallback: RecordingCallback,
         lastAccessIndex: Int,
         commitCallback: Runnable?
diff --git a/paging/runtime/src/main/java/androidx/paging/LivePagedList.kt b/paging/runtime/src/main/java/androidx/paging/LivePagedList.kt
index ab56fae..29cd5d2 100644
--- a/paging/runtime/src/main/java/androidx/paging/LivePagedList.kt
+++ b/paging/runtime/src/main/java/androidx/paging/LivePagedList.kt
@@ -41,7 +41,7 @@
     private val fetchDispatcher: CoroutineDispatcher
 ) : LiveData<PagedList<Value>>(
     InitialPagedList(
-        pagingSource = pagingSourceFactory(),
+        pagingSource = InitialPagingSource(),
         coroutineScope = coroutineScope,
         notifyDispatcher = notifyDispatcher,
         backgroundDispatcher = fetchDispatcher,
diff --git a/paging/runtime/src/main/java/androidx/paging/NullPaddedDiffing.md b/paging/runtime/src/main/java/androidx/paging/NullPaddedDiffing.md
new file mode 100644
index 0000000..2fecb52
--- /dev/null
+++ b/paging/runtime/src/main/java/androidx/paging/NullPaddedDiffing.md
@@ -0,0 +1,86 @@
+# Null Padded Diffing
+When placeholders are involved in the PagingSource, sending the diff to RecyclerView is not
+trivial and requires a heuristic. This is because the list may be arbitrarily large, and it isn't
+possible to resolve item identity from position, as null placeholders in the list may have resolved
+into loaded items.
+
+This document explains the algorithm and why it works that way.
+Notice that, due to lack of information on the paging side (even the PagingSource), it is
+impossible to create a solution that works for all cases. Instead, we do a best effort to cover
+as many common cases as we can.
+
+## Basic Algorithm
+The original algorithm, implemented [here](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:paging/runtime/src/main/java/androidx/paging/NullPaddedListDiffHelper.kt;drc=63c2310560810fd1ea3a7a90d15493d414ca090f)
+does the following:
+
+Given old list with `placeholders before: B0, items: I0, placeholders after:A0`
+and new list with `placeholders before: B1, items: I1, placeholders after:A1`
+It diffs `I0` and `I1` then adds/removes null items based on the difference between `B0` `B1` and
+`A0` `A1`.
+
+This has a couple of problems:
+* We don't send any change notifications for existing placeholders. As a result, if developer uses
+the position in the binding, it won't be rebound even though placeholder's position changed.
+* If a placeholder in the old list is now loaded, we don't send a `change` notification for it
+, instead, it either looks like the placehoder moved (if there are still enough placeholders after
+) or it got removed.
+* When the list size is the same (or similar) but a new disjoint range is loaded, RecyclerView
+ shifts.
+ For instance, if you have 100 items where only the range 20-30 is loaded,
+ If range 40-50 is loaded in the next one, hence:
+ `B0` = 20, `I0` = 10, `A0` = 70
+ `B1` = 40, `I0` = 10, `A0` = 50
+
+We dispatch `add 20 items to pos 0` and `remove 20 items from pos 80`. If the RecyclerView was
+showing placeholders `40-50`, it now thinks they are in positions `60-70` so scrolls to that
+position causing the effect of constantly moving towards the end of the list.
+
+Note that this is not necessarily **wrong**. For instance, maybe the last 20 items moved to the
+beginning of the list hence it could've been the right dispatch. This is impossible to know
+without actually loading placeholders which defeats the purpose of placeholders.
+The issue in the current implementation is that it does not optimize for the common case.
+
+## Goals of the new algorithm
+* Try to detect cases where we can match new loaded items to placeholders from the previous list
+ and send change notifications for them. Similarly, try to convert removals to placeholders when
+ possible.
+* Try to keep positions stable when distinct lists are loaded.
+* For placeholders that stay in the old & new list, send change notifications for them if their
+ positions changed to handle cases where developer uses bind position in the placeholder View.
+
+### How it works
+* Same as the first algorithm, we diff `I0` and `I1`.
+* Instead of dispatching updates with offset as the first algorithm does:
+  * If two lists do not overlap at all, use a special diffing where: (DistinctListsDiffDispatcher)
+    * We'll send change events for placeholders that became items (based on position)
+    * We'll send change events for items that became placeholders (based on position)
+    * We'll send add/remove for items that get swapped with other items (based on position)
+    * Finally, we'll add/remove placeholders **to the end** to fix the list size
+      * This heuristic optimizes for non-reverse layouts
+    * This will make RecyclerView re-layout from where it is, nicely handling jumps.
+  * If two lists overlap, we'll try to dispatch some of the insert/remove events from the I0-I1
+   diff as placeholder **change** events, when possible. This is specifically to cover cases
+   where placeholders are loaded as items in the updated list OR items in the old list are
+   unloaded (as placeholders).
+    * If addition/removal happened within the first and last item (so not the start or end of
+     loaded items) dispatch the event as is (with offset for placeholders).
+    * For other changes that happen at the edges of the list:
+      * For removals, if we will need more placeholders in that direction, dispatch **CHANGE** to
+       turn them into placeholders. If we have more removals then new placeholders needed, dispatch
+        **REMOVE** for them.
+      * For additions, if we have placeholders in that direction, dispatch **CHANGE** for those
+       placeholders to turn them into items. If we have more new items than existing placeholders
+       , dispatch **INSERT**.
+      * When we use placeholders in one edge to handle insert OR remove; we flag that edge as
+       "insertion" or "removal". When an edge is flagged as "insertion", it can only be used for
+       "insert" events later on. Similarly, if it is flagged as "removal", it can only be used
+        for "remove" again. This is to ensure that we never convert an item into placeholder
+        then back to an item (or vice versa). Doing that would mean sending two **CHANGE** events
+        for an item that will make RecyclerView think that item has been update even though it is
+        actually being swapped with a completely different item.
+    * Finally, dispatch add/remove events to match the placeholder count. We will insert at index
+     0 for leading placeholders and index(size) for trailing placeholders.
+    * For event placeholders that stayed in the list, dispatch a change if their positions changed.
+      * This technically mean we consider placeholder's position as part of its data. These
+       change notifications will direct RecyclerView to rebind them; handling cases where
+       developer uses binding position in the placeholder view.
\ No newline at end of file
diff --git a/paging/runtime/src/main/java/androidx/paging/NullPaddedListDiffHelper.kt b/paging/runtime/src/main/java/androidx/paging/NullPaddedListDiffHelper.kt
index 76e92a2..af09d964 100644
--- a/paging/runtime/src/main/java/androidx/paging/NullPaddedListDiffHelper.kt
+++ b/paging/runtime/src/main/java/androidx/paging/NullPaddedListDiffHelper.kt
@@ -16,8 +16,12 @@
 
 package androidx.paging
 
+import androidx.paging.DiffingChangePayload.ITEM_TO_PLACEHOLDER
+import androidx.paging.DiffingChangePayload.PLACEHOLDER_POSITION_CHANGE
+import androidx.paging.DiffingChangePayload.PLACEHOLDER_TO_ITEM
 import androidx.recyclerview.widget.DiffUtil
 import androidx.recyclerview.widget.ListUpdateCallback
+import androidx.recyclerview.widget.RecyclerView
 
 /**
  * Methods for computing and applying DiffResults between PagedLists.
@@ -34,11 +38,11 @@
 internal fun <T : Any> NullPaddedList<T>.computeDiff(
     newList: NullPaddedList<T>,
     diffCallback: DiffUtil.ItemCallback<T>
-): DiffUtil.DiffResult {
+): NullPaddedDiffResult {
     val oldSize = storageCount
     val newSize = newList.storageCount
 
-    return DiffUtil.calculateDiff(
+    val diffResult = DiffUtil.calculateDiff(
         object : DiffUtil.Callback() {
             override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
                 val oldItem = getFromStorage(oldItemPosition)
@@ -76,6 +80,14 @@
         },
         true
     )
+    // find first overlap
+    val hasOverlap = (0 until storageCount).any {
+        diffResult.convertOldPositionToNew(it) != RecyclerView.NO_POSITION
+    }
+    return NullPaddedDiffResult(
+        diff = diffResult,
+        hasOverlap = hasOverlap
+    )
 }
 
 private class OffsettingListUpdateCallback internal constructor(
@@ -100,12 +112,7 @@
 }
 
 /**
- * TODO: improve diffing logic
- *
- * This function currently does a naive diff, assuming null does not become an item, and vice
- * versa (so it won't dispatch onChange events for these). It's similar to passing a list with
- * leading/trailing nulls in the beginning / end to DiffUtil, but dispatches the remove/insert
- * for changed nulls at the beginning / end of the list.
+ * See NullPaddedDiffing.md for how this works and why it works that way :).
  *
  * Note: if lists mutate between diffing the snapshot and dispatching the diff here, then we
  * handle this by passing the snapshot to the callback, and dispatching those changes
@@ -114,43 +121,24 @@
 internal fun <T : Any> NullPaddedList<T>.dispatchDiff(
     callback: ListUpdateCallback,
     newList: NullPaddedList<T>,
-    diffResult: DiffUtil.DiffResult
+    diffResult: NullPaddedDiffResult
 ) {
-    val trailingOld = placeholdersAfter
-    val trailingNew = newList.placeholdersAfter
-    val leadingOld = placeholdersBefore
-    val leadingNew = newList.placeholdersBefore
-
-    if (trailingOld == 0 &&
-        trailingNew == 0 &&
-        leadingOld == 0 &&
-        leadingNew == 0
-    ) {
-        // Simple case, dispatch & return
-        diffResult.dispatchUpdatesTo(callback)
-        return
-    }
-
-    // First, remove or insert trailing nulls
-    if (trailingOld > trailingNew) {
-        val count = trailingOld - trailingNew
-        callback.onRemoved(size - count, count)
-    } else if (trailingOld < trailingNew) {
-        callback.onInserted(size, trailingNew - trailingOld)
-    }
-
-    // Second, remove or insert leading nulls
-    if (leadingOld > leadingNew) {
-        callback.onRemoved(0, leadingOld - leadingNew)
-    } else if (leadingOld < leadingNew) {
-        callback.onInserted(0, leadingNew - leadingOld)
-    }
-
-    // apply the diff, with an offset if needed
-    if (leadingNew != 0) {
-        diffResult.dispatchUpdatesTo(OffsettingListUpdateCallback(leadingNew, callback))
+    if (diffResult.hasOverlap) {
+        OverlappingListsDiffDispatcher.dispatchDiff(
+            oldList = this,
+            newList = newList,
+            callback = callback,
+            diffResult = diffResult,
+        )
     } else {
-        diffResult.dispatchUpdatesTo(callback)
+        // if no values overlapped between two lists, use change with payload *unless* the
+        // position represents real items in both old and new lists in which case *change* would
+        // be misleading hence we need to dispatch add/remove.
+        DistinctListsDiffDispatcher.dispatchDiff(
+            callback = callback,
+            oldList = this,
+            newList = newList,
+        )
     }
 }
 
@@ -159,10 +147,14 @@
  * after the diff, or a guess if it no longer exists.
  */
 internal fun NullPaddedList<*>.transformAnchorIndex(
-    diffResult: DiffUtil.DiffResult,
+    diffResult: NullPaddedDiffResult,
     newList: NullPaddedList<*>,
     oldPosition: Int
 ): Int {
+    if (!diffResult.hasOverlap) {
+        // if lists didn't overlap, use old position
+        return oldPosition.coerceIn(0 until newList.size)
+    }
     // diffResult's indices starting after nulls, need to transform to diffutil indices
     // (see also dispatchDiff(), which adds this offset when dispatching)
     val diffIndex = oldPosition - placeholdersBefore
@@ -180,7 +172,7 @@
                 continue
             }
 
-            val result = diffResult.convertOldPositionToNew(positionToTry)
+            val result = diffResult.diff.convertOldPositionToNew(positionToTry)
             if (result != -1) {
                 // also need to transform from diffutil output indices to newList
                 return result + newList.placeholdersBefore
@@ -191,3 +183,370 @@
     // not anchored to an item in new list, so just reuse position (clamped to newList size)
     return oldPosition.coerceIn(0 until newList.size)
 }
+
+internal class NullPaddedDiffResult(
+    val diff: DiffUtil.DiffResult,
+    // true if two lists have at least 1 item the same
+    val hasOverlap: Boolean
+)
+
+/**
+ * Helper class to implement the heuristic documented in NullPaddedDiffing.md.
+ */
+internal object OverlappingListsDiffDispatcher {
+    fun <T> dispatchDiff(
+        oldList: NullPaddedList<T>,
+        newList: NullPaddedList<T>,
+        callback: ListUpdateCallback,
+        diffResult: NullPaddedDiffResult
+    ) {
+        val callbackWrapper = PlaceholderUsingUpdateCallback(
+            oldList = oldList,
+            newList = newList,
+            callback = callback
+        )
+        diffResult.diff.dispatchUpdatesTo(callbackWrapper)
+        callbackWrapper.fixPlaceholders()
+    }
+
+    @Suppress("NOTHING_TO_INLINE")
+    private class PlaceholderUsingUpdateCallback<T>(
+        private val oldList: NullPaddedList<T>,
+        private val newList: NullPaddedList<T>,
+        private val callback: ListUpdateCallback
+    ) : ListUpdateCallback {
+        // These variables hold the "current" value for placeholders and storage count and are
+        // updated as we dispatch notify events to `callback`.
+        private var placeholdersBefore = oldList.placeholdersBefore
+        private var placeholdersAfter = oldList.placeholdersAfter
+        private var storageCount = oldList.storageCount
+
+        // Track if we used placeholders for a certain case to avoid using them for both additions
+        // and removals at the same time, which might end up sending misleading change events.
+        private var placeholdersBeforeState = UNUSED
+        private var placeholdersAfterState = UNUSED
+
+        /**
+         * Offsets a value based on placeholders to make it suitable to pass into the callback.
+         */
+        private inline fun Int.offsetForDispatch() = this + placeholdersBefore
+
+        fun fixPlaceholders() {
+            // add / remove placeholders to match the new list
+            fixLeadingPlaceholders()
+            fixTrailingPlaceholders()
+        }
+
+        private fun fixTrailingPlaceholders() {
+            // the #of placeholders that didn't have any updates. We might need to send position
+            // change events for them if their original positions are no longer valid.
+            var unchangedPlaceholders = minOf(oldList.placeholdersAfter, placeholdersAfter)
+
+            val postPlaceholdersToAdd = newList.placeholdersAfter - placeholdersAfter
+            val runningListSize = placeholdersBefore + storageCount + placeholdersAfter
+            // check if unchanged placeholders changed their positions between two lists
+            val unchangedPlaceholdersStartPos = runningListSize - unchangedPlaceholders
+            val unchangedPlaceholdersMoved =
+                unchangedPlaceholdersStartPos != (oldList.size - unchangedPlaceholders)
+            if (postPlaceholdersToAdd > 0) {
+                // always add to the end of the list
+                callback.onInserted(runningListSize, postPlaceholdersToAdd)
+            } else if (postPlaceholdersToAdd < 0) {
+                // always remove from the end
+                // notice that postPlaceholdersToAdd is negative, thats why it is added to
+                // runningListEnd
+                callback.onRemoved(
+                    runningListSize + postPlaceholdersToAdd,
+                    -postPlaceholdersToAdd
+                )
+                // remove them from unchanged placeholders, notice that it is an addition because
+                // postPlaceholdersToAdd is negative
+                unchangedPlaceholders += postPlaceholdersToAdd
+            }
+            if (unchangedPlaceholders > 0 && unchangedPlaceholdersMoved) {
+                // These placeholders didn't get any change event yet their list positions changed.
+                // We should send an update as the position of a placeholder is part of its data.
+                callback.onChanged(
+                    unchangedPlaceholdersStartPos,
+                    unchangedPlaceholders,
+                    PLACEHOLDER_POSITION_CHANGE
+                )
+            }
+            placeholdersAfter = newList.placeholdersAfter
+        }
+
+        private fun fixLeadingPlaceholders() {
+            // the #of placeholders that didn't have any updates. We might need to send position
+            // change events if we further modify the list.
+            val unchangedPlaceholders = minOf(oldList.placeholdersBefore, placeholdersBefore)
+            val prePlaceholdersToAdd = newList.placeholdersBefore - placeholdersBefore
+            if (prePlaceholdersToAdd > 0) {
+                if (unchangedPlaceholders > 0) {
+                    // these will be shifted down so send a change event for them
+                    callback.onChanged(0, unchangedPlaceholders, PLACEHOLDER_POSITION_CHANGE)
+                }
+                // always insert to the beginning of the list
+                callback.onInserted(0, prePlaceholdersToAdd)
+            } else if (prePlaceholdersToAdd < 0) {
+                // always remove from the beginning of the list
+                callback.onRemoved(0, -prePlaceholdersToAdd)
+                if (unchangedPlaceholders + prePlaceholdersToAdd > 0) {
+                    // these have been shifted up, send a change event for them. We add the negative
+                    // number of `prePlaceholdersToAdd` not to send change events for them
+                    callback.onChanged(
+                        0, unchangedPlaceholders + prePlaceholdersToAdd,
+                        PLACEHOLDER_POSITION_CHANGE
+                    )
+                }
+            }
+            placeholdersBefore = newList.placeholdersBefore
+        }
+
+        override fun onInserted(position: Int, count: Int) {
+            when {
+                dispatchInsertAsPlaceholderAfter(position, count) -> {
+                    // dispatched as placeholders after
+                }
+                dispatchInsertAsPlaceholderBefore(position, count) -> {
+                    // dispatched as placeholders before
+                }
+                else -> {
+                    // not at the edge, dispatch as usual
+                    callback.onInserted(position.offsetForDispatch(), count)
+                }
+            }
+            storageCount += count
+        }
+
+        /**
+         * Return true if it is dispatched, false otherwise.
+         */
+        private fun dispatchInsertAsPlaceholderBefore(position: Int, count: Int): Boolean {
+            if (position > 0) {
+                return false // not at the edge
+            }
+            if (placeholdersBeforeState == USED_FOR_REMOVAL) {
+                return false
+            }
+            val asPlaceholderChange = minOf(count, placeholdersBefore)
+            if (asPlaceholderChange > 0) {
+                placeholdersBeforeState = USED_FOR_ADDITION
+                // this index is negative because we are going back. offsetForDispatch will fix it
+                val index = (0 - asPlaceholderChange)
+                callback.onChanged(
+                    index.offsetForDispatch(), asPlaceholderChange, PLACEHOLDER_TO_ITEM
+                )
+                placeholdersBefore -= asPlaceholderChange
+            }
+            val asInsert = count - asPlaceholderChange
+            if (asInsert > 0) {
+                callback.onInserted(
+                    0.offsetForDispatch(), asInsert
+                )
+            }
+            return true
+        }
+
+        /**
+         * Return true if it is dispatched, false otherwise.
+         */
+        private fun dispatchInsertAsPlaceholderAfter(position: Int, count: Int): Boolean {
+            if (position < storageCount) {
+                return false // not at the edge
+            }
+            if (placeholdersAfterState == USED_FOR_REMOVAL) {
+                return false
+            }
+            val asPlaceholderChange = minOf(count, placeholdersAfter)
+            if (asPlaceholderChange > 0) {
+                placeholdersAfterState = USED_FOR_ADDITION
+                callback.onChanged(
+                    position.offsetForDispatch(), asPlaceholderChange, PLACEHOLDER_TO_ITEM
+                )
+                placeholdersAfter -= asPlaceholderChange
+            }
+            val asInsert = count - asPlaceholderChange
+            if (asInsert > 0) {
+                callback.onInserted(
+                    (position + asPlaceholderChange).offsetForDispatch(), asInsert
+                )
+            }
+            return true
+        }
+
+        override fun onRemoved(position: Int, count: Int) {
+            when {
+                dispatchRemovalAsPlaceholdersAfter(position, count) -> {
+                    // dispatched as changed into placeholder
+                }
+                dispatchRemovalAsPlaceholdersBefore(position, count) -> {
+                    // dispatched as changed into placeholder
+                }
+                else -> {
+                    // fallback, need to handle here
+                    callback.onRemoved(position.offsetForDispatch(), count)
+                }
+            }
+            storageCount -= count
+        }
+
+        /**
+         * Return true if it is dispatched, false otherwise.
+         */
+        private fun dispatchRemovalAsPlaceholdersBefore(position: Int, count: Int): Boolean {
+            if (position > 0) {
+                return false
+            }
+            if (placeholdersBeforeState == USED_FOR_ADDITION) {
+                return false
+            }
+            // see how many removals we can convert to change.
+            // make sure we don't end up having too many placeholders that we'll end up removing
+            // anyways
+            val maxPlaceholdersToAdd = newList.placeholdersBefore - placeholdersBefore
+            val asPlaceholders = minOf(maxPlaceholdersToAdd, count).coerceAtLeast(0)
+            val asRemoval = count - asPlaceholders
+            // first remove then use placeholders to make sure items that are closer to the loaded
+            // content center are more likely to stay in the list
+            if (asRemoval > 0) {
+                callback.onRemoved(0.offsetForDispatch(), asRemoval)
+            }
+            if (asPlaceholders > 0) {
+                placeholdersBeforeState = USED_FOR_REMOVAL
+                callback.onChanged(
+                    0.offsetForDispatch(),
+                    asPlaceholders,
+                    ITEM_TO_PLACEHOLDER
+                )
+                placeholdersBefore += asPlaceholders
+            }
+            return true
+        }
+
+        /**
+         * Return true if it is dispatched, false otherwise.
+         */
+        private fun dispatchRemovalAsPlaceholdersAfter(position: Int, count: Int): Boolean {
+            val end = position + count
+            if (end < storageCount) {
+                return false // not at the edge
+            }
+            if (placeholdersAfterState == USED_FOR_ADDITION) {
+                return false
+            }
+            // see how many removals we can convert to change.
+            // make sure we don't end up having too many placeholders that we'll end up removing
+            // anyways
+            val maxPlaceholdersToAdd = newList.placeholdersAfter - placeholdersAfter
+            val asPlaceholders = minOf(maxPlaceholdersToAdd, count).coerceAtLeast(0)
+            val asRemoval = count - asPlaceholders
+            // first use placeholders then removal to make sure items that are closer to
+            // the loaded content center are more likely to stay in the list
+            if (asPlaceholders > 0) {
+                placeholdersAfterState = USED_FOR_REMOVAL
+                callback.onChanged(
+                    position.offsetForDispatch(),
+                    asPlaceholders,
+                    ITEM_TO_PLACEHOLDER
+                )
+                placeholdersAfter += asPlaceholders
+            }
+            if (asRemoval > 0) {
+                callback.onRemoved((position + asPlaceholders).offsetForDispatch(), asRemoval)
+            }
+            return true
+        }
+
+        override fun onMoved(fromPosition: Int, toPosition: Int) {
+            callback.onMoved(fromPosition.offsetForDispatch(), toPosition.offsetForDispatch())
+        }
+
+        override fun onChanged(position: Int, count: Int, payload: Any?) {
+            callback.onChanged(position.offsetForDispatch(), count, payload)
+        }
+
+        companion object {
+            // markers for edges to avoid using them for both additions and removals
+            private const val UNUSED = 1
+            private const val USED_FOR_REMOVAL = UNUSED + 1
+            private const val USED_FOR_ADDITION = USED_FOR_REMOVAL + 1
+        }
+    }
+}
+
+/**
+ * Helper object to dispatch diffs when two lists do not overlap at all.
+ *
+ * We try to send change events when an item's position is replaced with a placeholder or vice
+ * versa.
+ * If there is an item in a given position in before and after lists, we dispatch add/remove for
+ * them not to trigger unexpected change animations.
+ */
+internal object DistinctListsDiffDispatcher {
+    fun <T : Any> dispatchDiff(
+        callback: ListUpdateCallback,
+        oldList: NullPaddedList<T>,
+        newList: NullPaddedList<T>,
+    ) {
+        val storageOverlapStart = maxOf(
+            oldList.placeholdersBefore, newList.placeholdersBefore
+        )
+        val storageOverlapEnd = minOf(
+            oldList.placeholdersBefore + oldList.storageCount,
+            newList.placeholdersBefore + newList.storageCount
+        )
+        // we need to dispatch add/remove for overlapping storage positions
+        val overlappingStorageSize = storageOverlapEnd - storageOverlapStart
+        if (overlappingStorageSize > 0) {
+            callback.onRemoved(storageOverlapStart, overlappingStorageSize)
+            callback.onInserted(storageOverlapStart, overlappingStorageSize)
+        }
+        // now everything else is good as a change animation.
+        // make sure to send a change for old items whose positions are still in the list
+        // to handle cases where there is no overlap, we min/max boundaries
+        val changeEventStartBoundary = minOf(storageOverlapStart, storageOverlapEnd)
+        val changeEventEndBoundary = maxOf(storageOverlapStart, storageOverlapEnd)
+        dispatchChange(
+            callback = callback,
+            startBoundary = changeEventStartBoundary,
+            endBoundary = changeEventEndBoundary,
+            start = oldList.placeholdersBefore.coerceAtMost(newList.size),
+            end = (oldList.placeholdersBefore + oldList.storageCount).coerceAtMost(newList.size),
+            payload = ITEM_TO_PLACEHOLDER
+        )
+        // now for new items that were mapping to placeholders, send change events
+        dispatchChange(
+            callback = callback,
+            startBoundary = changeEventStartBoundary,
+            endBoundary = changeEventEndBoundary,
+            start = newList.placeholdersBefore.coerceAtMost(oldList.size),
+            end = (newList.placeholdersBefore + newList.storageCount).coerceAtMost(oldList.size),
+            payload = PLACEHOLDER_TO_ITEM
+        )
+        // finally, fix the size
+        val itemsToAdd = newList.size - oldList.size
+        if (itemsToAdd > 0) {
+            callback.onInserted(oldList.size, itemsToAdd)
+        } else if (itemsToAdd < 0) {
+            callback.onRemoved(oldList.size + itemsToAdd, -itemsToAdd)
+        }
+    }
+
+    private fun dispatchChange(
+        callback: ListUpdateCallback,
+        startBoundary: Int,
+        endBoundary: Int,
+        start: Int,
+        end: Int,
+        payload: Any
+    ) {
+        val beforeOverlapCount = startBoundary - start
+        if (beforeOverlapCount > 0) {
+            callback.onChanged(start, beforeOverlapCount, payload)
+        }
+        val afterOverlapCount = end - endBoundary
+        if (afterOverlapCount > 0) {
+            callback.onChanged(endBoundary, afterOverlapCount, payload)
+        }
+    }
+}
diff --git a/paging/rxjava2/api/3.0.0-beta03.txt b/paging/rxjava2/api/3.0.0-beta03.txt
new file mode 100644
index 0000000..edfcce1
--- /dev/null
+++ b/paging/rxjava2/api/3.0.0-beta03.txt
@@ -0,0 +1,53 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  @Deprecated public final class RxPagedListBuilder<Key, Value> {
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    method @Deprecated public io.reactivex.Flowable<androidx.paging.PagedList<Value>> buildFlowable(io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public io.reactivex.Observable<androidx.paging.PagedList<Value>> buildObservable();
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setFetchScheduler(io.reactivex.Scheduler scheduler);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setNotifyScheduler(io.reactivex.Scheduler scheduler);
+  }
+
+  public final class RxPagedListKt {
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+  }
+
+}
+
+package androidx.paging.rxjava2 {
+
+  public final class PagingRx {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Iterable<R>>> transform);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<R>> transform);
+  }
+
+  public final class PagingRx {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Iterable<R>>> transform);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<R>> transform);
+  }
+
+  public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public RxPagingSource();
+    method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public abstract io.reactivex.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+}
+
diff --git a/paging/rxjava2/api/public_plus_experimental_3.0.0-beta03.txt b/paging/rxjava2/api/public_plus_experimental_3.0.0-beta03.txt
new file mode 100644
index 0000000..c7ed5fc
--- /dev/null
+++ b/paging/rxjava2/api/public_plus_experimental_3.0.0-beta03.txt
@@ -0,0 +1,69 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  @Deprecated public final class RxPagedListBuilder<Key, Value> {
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    method @Deprecated public io.reactivex.Flowable<androidx.paging.PagedList<Value>> buildFlowable(io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public io.reactivex.Observable<androidx.paging.PagedList<Value>> buildObservable();
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setFetchScheduler(io.reactivex.Scheduler scheduler);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setNotifyScheduler(io.reactivex.Scheduler scheduler);
+  }
+
+  public final class RxPagedListKt {
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+  }
+
+}
+
+package androidx.paging.rxjava2 {
+
+  public final class PagingRx {
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.Flowable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.Flowable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.Observable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.Observable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Iterable<R>>> transform);
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <Key, Value> io.reactivex.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<R>> transform);
+  }
+
+  public final class PagingRx {
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.Flowable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.Flowable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.Observable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.Observable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Iterable<R>>> transform);
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <Key, Value> io.reactivex.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<R>> transform);
+  }
+
+  public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public RxPagingSource();
+    method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public abstract io.reactivex.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+  @androidx.paging.ExperimentalPagingApi public abstract class RxRemoteMediator<Key, Value> extends androidx.paging.RemoteMediator<Key,Value> {
+    ctor public RxRemoteMediator();
+    method public final suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction> p);
+    method public io.reactivex.Single<androidx.paging.RemoteMediator.InitializeAction> initializeSingle();
+    method public final suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult> p);
+    method public abstract io.reactivex.Single<androidx.paging.RemoteMediator.MediatorResult> loadSingle(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state);
+  }
+
+}
+
diff --git a/paging/rxjava2/api/res-3.0.0-beta03.txt b/paging/rxjava2/api/res-3.0.0-beta03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/rxjava2/api/res-3.0.0-beta03.txt
diff --git a/paging/rxjava2/api/restricted_3.0.0-beta03.txt b/paging/rxjava2/api/restricted_3.0.0-beta03.txt
new file mode 100644
index 0000000..edfcce1
--- /dev/null
+++ b/paging/rxjava2/api/restricted_3.0.0-beta03.txt
@@ -0,0 +1,53 @@
+// Signature format: 4.0
+package androidx.paging {
+
+  @Deprecated public final class RxPagedListBuilder<Key, Value> {
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>> pagingSourceFactory, int pageSize);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, androidx.paging.PagedList.Config config);
+    ctor @Deprecated public RxPagedListBuilder(androidx.paging.DataSource.Factory<Key,Value> dataSourceFactory, int pageSize);
+    method @Deprecated public io.reactivex.Flowable<androidx.paging.PagedList<Value>> buildFlowable(io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public io.reactivex.Observable<androidx.paging.PagedList<Value>> buildObservable();
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setFetchScheduler(io.reactivex.Scheduler scheduler);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setInitialLoadKey(Key? key);
+    method @Deprecated public androidx.paging.RxPagedListBuilder<Key,Value> setNotifyScheduler(io.reactivex.Scheduler scheduler);
+  }
+
+  public final class RxPagedListKt {
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Flowable<androidx.paging.PagedList<Value>> toFlowable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler, optional io.reactivex.BackpressureStrategy backpressureStrategy);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(androidx.paging.DataSource.Factory<Key,Value>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, androidx.paging.PagedList.Config config, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+    method @Deprecated public static <Key, Value> io.reactivex.Observable<androidx.paging.PagedList<Value>> toObservable(kotlin.jvm.functions.Function0<? extends androidx.paging.PagingSource<Key,Value>>, int pageSize, optional Key? initialLoadKey, optional androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback, optional io.reactivex.Scheduler? fetchScheduler, optional io.reactivex.Scheduler? notifyScheduler);
+  }
+
+}
+
+package androidx.paging.rxjava2 {
+
+  public final class PagingRx {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Iterable<R>>> transform);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<R>> transform);
+  }
+
+  public final class PagingRx {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<java.lang.Iterable<R>>> transform);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.Single<R>> transform);
+  }
+
+  public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public RxPagingSource();
+    method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public abstract io.reactivex.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+}
+
diff --git a/paging/rxjava2/ktx/api/3.0.0-beta03.txt b/paging/rxjava2/ktx/api/3.0.0-beta03.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/rxjava2/ktx/api/3.0.0-beta03.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/rxjava2/ktx/api/public_plus_experimental_3.0.0-beta03.txt b/paging/rxjava2/ktx/api/public_plus_experimental_3.0.0-beta03.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/rxjava2/ktx/api/public_plus_experimental_3.0.0-beta03.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/rxjava2/ktx/api/res-3.0.0-beta03.txt b/paging/rxjava2/ktx/api/res-3.0.0-beta03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/rxjava2/ktx/api/res-3.0.0-beta03.txt
diff --git a/paging/rxjava2/ktx/api/restricted_3.0.0-beta03.txt b/paging/rxjava2/ktx/api/restricted_3.0.0-beta03.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/paging/rxjava2/ktx/api/restricted_3.0.0-beta03.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/paging/rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.kt b/paging/rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.kt
index 2959747..d721431 100644
--- a/paging/rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.kt
+++ b/paging/rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.kt
@@ -356,7 +356,7 @@
 
         init {
             currentData = InitialPagedList(
-                pagingSource = pagingSourceFactory(),
+                pagingSource = InitialPagingSource(),
                 coroutineScope = GlobalScope,
                 notifyDispatcher = notifyDispatcher,
                 backgroundDispatcher = fetchDispatcher,
diff --git a/paging/rxjava2/src/test/java/androidx/paging/RxPagedListBuilderTest.kt b/paging/rxjava2/src/test/java/androidx/paging/RxPagedListBuilderTest.kt
index ddac5e8..6607f10 100644
--- a/paging/rxjava2/src/test/java/androidx/paging/RxPagedListBuilderTest.kt
+++ b/paging/rxjava2/src/test/java/androidx/paging/RxPagedListBuilderTest.kt
@@ -29,6 +29,7 @@
 import org.junit.runners.JUnit4
 import kotlin.test.assertTrue
 
+@Suppress("DEPRECATION")
 @RunWith(JUnit4::class)
 class RxPagedListBuilderTest {
     private data class LoadStateEvent(
@@ -101,20 +102,17 @@
     fun basic() {
         val factory = testDataSourceSequence(
             listOf(
-                listOf(), // first used by InitialPagedList
                 listOf("a", "b"),
                 listOf("c", "d")
             )
         )
         val scheduler = TestScheduler()
 
-        @Suppress("DEPRECATION")
         val observable = RxPagedListBuilder(factory, 10)
             .setFetchScheduler(scheduler)
             .setNotifyScheduler(scheduler)
             .buildObservable()
 
-        @Suppress("DEPRECATION")
         val observer = TestObserver<PagedList<String>>()
 
         observable.subscribe(observer)
@@ -130,7 +128,6 @@
         assertEquals(listOf("a", "b"), observer.values().last())
 
         // invalidate triggers second load
-        @Suppress("DEPRECATION")
         observer.values().last().dataSource.invalidate()
         scheduler.triggerActions()
         observer.assertValueCount(3)
@@ -144,13 +141,11 @@
         val notifyScheduler = TestScheduler()
         val fetchScheduler = TestScheduler()
 
-        @Suppress("DEPRECATION")
         val observable: Observable<PagedList<String>> = RxPagedListBuilder(factory, 10)
             .setFetchScheduler(fetchScheduler)
             .setNotifyScheduler(notifyScheduler)
             .buildObservable()
 
-        @Suppress("DEPRECATION")
         val observer = TestObserver<PagedList<String>>()
         observable.subscribe(observer)
 
@@ -177,13 +172,11 @@
         val notifyScheduler = TestScheduler()
         val fetchScheduler = TestScheduler()
 
-        @Suppress("DEPRECATION")
         val observable = RxPagedListBuilder(factory::create, 2)
             .setFetchScheduler(fetchScheduler)
             .setNotifyScheduler(notifyScheduler)
             .buildObservable()
 
-        @Suppress("DEPRECATION")
         val observer = TestObserver<PagedList<String>>()
         observable.subscribe(observer)
 
@@ -261,6 +254,34 @@
         )
     }
 
+    @Test
+    fun instantiatesPagingSourceOnFetchDispatcher() {
+        var pagingSourcesCreated = 0
+        val pagingSourceFactory = {
+            pagingSourcesCreated++
+            TestPagingSource()
+        }
+        val notifyScheduler = TestScheduler()
+        val fetchScheduler = TestScheduler()
+        val rxPagedList = RxPagedListBuilder(
+            pagingSourceFactory = pagingSourceFactory,
+            pageSize = 10,
+        ).apply {
+            setNotifyScheduler(notifyScheduler)
+            setFetchScheduler(fetchScheduler)
+        }.buildObservable()
+
+        fetchScheduler.triggerActions()
+        assertEquals(0, pagingSourcesCreated)
+
+        rxPagedList.subscribe { }
+
+        assertEquals(0, pagingSourcesCreated)
+
+        fetchScheduler.triggerActions()
+        assertEquals(1, pagingSourcesCreated)
+    }
+
     companion object {
         val EXCEPTION = Exception("")
     }
diff --git a/paging/rxjava3/api/3.0.0-beta03.txt b/paging/rxjava3/api/3.0.0-beta03.txt
new file mode 100644
index 0000000..45bdff1f
--- /dev/null
+++ b/paging/rxjava3/api/3.0.0-beta03.txt
@@ -0,0 +1,25 @@
+// Signature format: 4.0
+package androidx.paging.rxjava3 {
+
+  public final class PagingRx {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Iterable<R>>> transform);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.rxjava3.core.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<R>> transform);
+  }
+
+  public final class PagingRx {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Iterable<R>>> transform);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.rxjava3.core.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<R>> transform);
+  }
+
+  public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public RxPagingSource();
+    method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+}
+
diff --git a/paging/rxjava3/api/public_plus_experimental_3.0.0-beta03.txt b/paging/rxjava3/api/public_plus_experimental_3.0.0-beta03.txt
new file mode 100644
index 0000000..a6ca4be
--- /dev/null
+++ b/paging/rxjava3/api/public_plus_experimental_3.0.0-beta03.txt
@@ -0,0 +1,41 @@
+// Signature format: 4.0
+package androidx.paging.rxjava3 {
+
+  public final class PagingRx {
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Iterable<R>>> transform);
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.rxjava3.core.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<R>> transform);
+  }
+
+  public final class PagingRx {
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <T> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Iterable<R>>> transform);
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+    method @kotlinx.coroutines.ExperimentalCoroutinesApi public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.rxjava3.core.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<R>> transform);
+  }
+
+  public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public RxPagingSource();
+    method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+  @androidx.paging.ExperimentalPagingApi public abstract class RxRemoteMediator<Key, Value> extends androidx.paging.RemoteMediator<Key,Value> {
+    ctor public RxRemoteMediator();
+    method public final suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction> p);
+    method public io.reactivex.rxjava3.core.Single<androidx.paging.RemoteMediator.InitializeAction> initializeSingle();
+    method public final suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult> p);
+    method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.RemoteMediator.MediatorResult> loadSingle(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state);
+  }
+
+}
+
diff --git a/paging/rxjava3/api/res-3.0.0-beta03.txt b/paging/rxjava3/api/res-3.0.0-beta03.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/rxjava3/api/res-3.0.0-beta03.txt
diff --git a/paging/rxjava3/api/restricted_3.0.0-beta03.txt b/paging/rxjava3/api/restricted_3.0.0-beta03.txt
new file mode 100644
index 0000000..45bdff1f
--- /dev/null
+++ b/paging/rxjava3/api/restricted_3.0.0-beta03.txt
@@ -0,0 +1,25 @@
+// Signature format: 4.0
+package androidx.paging.rxjava3 {
+
+  public final class PagingRx {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Iterable<R>>> transform);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.rxjava3.core.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<R>> transform);
+  }
+
+  public final class PagingRx {
+    method @CheckResult public static <T> androidx.paging.PagingData<T> filter(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Boolean>> predicate);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> flatMap(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<java.lang.Iterable<R>>> transform);
+    method @CheckResult public static <T extends R, R> androidx.paging.PagingData<R> insertSeparators(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function2<? super T,? super T,? extends io.reactivex.rxjava3.core.Maybe<R>> generator);
+    method @CheckResult public static <T, R> androidx.paging.PagingData<R> map(androidx.paging.PagingData<T>, kotlin.jvm.functions.Function1<? super T,? extends io.reactivex.rxjava3.core.Single<R>> transform);
+  }
+
+  public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+    ctor public RxPagingSource();
+    method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+    method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+  }
+
+}
+
diff --git a/paging/settings.gradle b/paging/settings.gradle
index 29b5522b..234ca47 100644
--- a/paging/settings.gradle
+++ b/paging/settings.gradle
@@ -20,11 +20,12 @@
 setupPlayground(this, "..")
 selectProjectsFromAndroidX({ name ->
     if (name.startsWith(":paging")) return true
-    if (name == ":compose:integration-tests:demos:common") return true
     if (name == ":annotation:annotation-sampled") return true
     if (name == ":internal-testutils-ktx") return true
     if (name == ":internal-testutils-paging") return true
-    if (name == ":compose:internal-lint-checks") return true
+    if (name == ":compose:integration-tests:demos:common") return true
+    if (name == ":compose:lint:common") return true
+    if (name == ":compose:lint:internal-lint-checks") return true
     return false
 })
 
diff --git a/placeholder-tests/lint-baseline.xml b/placeholder-tests/lint-baseline.xml
deleted file mode 100644
index 8f1aa4b..0000000
--- a/placeholder-tests/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-</issues>
diff --git a/playground-common/playground-include-settings.gradle b/playground-common/playground-include-settings.gradle
index 4f2e033..994668d 100644
--- a/playground-common/playground-include-settings.gradle
+++ b/playground-common/playground-include-settings.gradle
@@ -65,6 +65,8 @@
     }
     settings.includeBuild(new File(supportRoot, "androidx-plugin"))
     settings.includeProject(":lint-checks", new File(supportRoot, "lint-checks"))
+    settings.includeProject(":lint-checks:integration-tests",
+            new File(supportRoot, "lint-checks/integration-tests"))
     settings.includeProject(":fakeannotations", new File(supportRoot,"fakeannotations"))
     settings.includeProject(":internal-testutils-common",
             new File(supportRoot, "testutils/testutils-common"))
diff --git a/playground-common/playground.properties b/playground-common/playground.properties
index 50d7865..517b25a 100644
--- a/playground-common/playground.properties
+++ b/playground-common/playground.properties
@@ -28,7 +28,7 @@
 androidx.enableDocumentation=false
 # Disable coverage
 androidx.coverageEnabled=false
-androidx.playground.snapshotBuildId=7178218
-androidx.playground.metalavaBuildId=7176564
-androidx.playground.dokkaBuildId=7151221
+androidx.playground.snapshotBuildId=7208866
+androidx.playground.metalavaBuildId=7204513
+androidx.playground.dokkaBuildId=7180581
 androidx.studio.type=playground
diff --git a/preference/preference/res/values-vi/strings.xml b/preference/preference/res/values-vi/strings.xml
index a437be2..e80e75c 100644
--- a/preference/preference/res/values-vi/strings.xml
+++ b/preference/preference/res/values-vi/strings.xml
@@ -6,6 +6,6 @@
     <string name="expand_button_title" msgid="2427401033573778270">"Nâng cao"</string>
     <string name="summary_collapsed_preference_list" msgid="9167775378838880170">"<xliff:g id="CURRENT_ITEMS">%1$s</xliff:g>, <xliff:g id="ADDED_ITEMS">%2$s</xliff:g>"</string>
     <string name="copy" msgid="6083905920877235314">"Sao chép"</string>
-    <string name="preference_copied" msgid="6685851473431805375">"Đã sao chép \"<xliff:g id="SUMMARY">%1$s</xliff:g>\" vào khay nhớ tạm."</string>
+    <string name="preference_copied" msgid="6685851473431805375">"Đã sao chép \"<xliff:g id="SUMMARY">%1$s</xliff:g>\" vào bảng nhớ tạm."</string>
     <string name="not_set" msgid="6573031135582639649">"Chưa đặt"</string>
 </resources>
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/ConcatAdapterTest.kt b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/ConcatAdapterTest.kt
index 01619d2..38f728d 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/ConcatAdapterTest.kt
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/ConcatAdapterTest.kt
@@ -1153,6 +1153,17 @@
         )
     }
 
+    @Test
+    public fun builderDefaults() {
+        val defaultBuilder = Builder().build()
+        assertThat(defaultBuilder.isolateViewTypes).isEqualTo(
+            ConcatAdapter.Config.DEFAULT.isolateViewTypes
+        )
+        assertThat(defaultBuilder.stableIdMode).isEqualTo(
+            ConcatAdapter.Config.DEFAULT.stableIdMode
+        )
+    }
+
     private var itemCounter = 0
     private fun produceItem(): TestItem = (itemCounter++).let {
         TestItem(id = it, value = it)
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/ConcatAdapter.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/ConcatAdapter.java
index 21a9492..e2bb204 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/ConcatAdapter.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/ConcatAdapter.java
@@ -353,6 +353,11 @@
         public final StableIdMode stableIdMode;
 
 
+        /**
+         * Default configuration for {@link ConcatAdapter} where {@link Config#isolateViewTypes}
+         * is set to {@code true} and {@link Config#stableIdMode} is set to
+         * {@link StableIdMode#NO_STABLE_IDS}.
+         */
         @NonNull
         public static final Config DEFAULT = new Config(true, NO_STABLE_IDS);
 
@@ -403,8 +408,8 @@
          * The builder for {@link Config} class.
          */
         public static final class Builder {
-            private boolean mIsolateViewTypes;
-            private StableIdMode mStableIdMode = NO_STABLE_IDS;
+            private boolean mIsolateViewTypes = DEFAULT.isolateViewTypes;
+            private StableIdMode mStableIdMode = DEFAULT.stableIdMode;
 
             /**
              * Sets whether {@link ConcatAdapter} should isolate view types of nested adapters from
@@ -413,7 +418,8 @@
              * @param isolateViewTypes {@code true} if {@link ConcatAdapter} should override view
              *                         types of nested adapters to avoid view type
              *                         conflicts, {@code false} otherwise.
-             *                         Defaults to true.
+             *                         Defaults to {@link Config#DEFAULT}'s
+             *                         {@link Config#isolateViewTypes} value ({@code true}).
              * @return this
              * @see Config#isolateViewTypes
              */
@@ -429,7 +435,8 @@
              * for details.
              *
              * @param stableIdMode The stable id mode for the {@link ConcatAdapter}. Defaults to
-             *                     {@link StableIdMode#NO_STABLE_IDS}.
+             *                     {@link Config#DEFAULT}'s {@link Config#stableIdMode} value
+             *                     ({@link StableIdMode#NO_STABLE_IDS}).
              * @return this
              * @see Config#stableIdMode
              */
diff --git a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
index 182a8e4..395e4b5 100644
--- a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
+++ b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
@@ -427,7 +427,10 @@
                         Source.loadJavaSource(sourceFile, qName)
                     }
                     sourceFile.name.endsWith(".kt") -> {
-                        Source.loadKotlinSource(sourceFile)
+                        val relativePath = sourceFile.absolutePath.substringAfter(
+                            srcRoot.absolutePath
+                        ).dropWhile { it == '/' }
+                        Source.loadKotlinSource(sourceFile, relativePath)
                     }
                     else -> null
                 }
diff --git a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/KotlinCompilationUtil.kt b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/KotlinCompilationUtil.kt
index 82f5446..5bc1d03 100644
--- a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/KotlinCompilationUtil.kt
+++ b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/KotlinCompilationUtil.kt
@@ -113,10 +113,7 @@
         val classpaths: MutableSet<String> = LinkedHashSet()
         while (true) {
             if (currentClassloader === systemClassLoader) {
-                classpaths.addAll(
-                    System.getProperty("java.class.path")
-                        .split(System.getProperty("path.separator"))
-                )
+                classpaths.addAll(getSystemClasspaths())
                 break
             }
             if (currentClassloader === platformClassLoader) {
diff --git a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/Source.kt b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/Source.kt
index ec7f8f94..1f28f13 100644
--- a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/Source.kt
+++ b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/Source.kt
@@ -126,10 +126,11 @@
         }
 
         fun loadKotlinSource(
-            file: File
+            file: File,
+            relativePath: String
         ): Source {
             check(file.exists() && file.name.endsWith(".kt"))
-            return kotlin(file.absolutePath, file.readText())
+            return kotlin(relativePath, file.readText())
         }
 
         fun loadJavaSource(
@@ -142,13 +143,14 @@
 
         fun load(
             file: File,
-            qName: String
+            qName: String,
+            relativePath: String
         ): Source {
             check(file.exists()) {
                 "file does not exist: ${file.absolutePath}"
             }
             return when {
-                file.name.endsWith(".kt") -> loadKotlinSource(file)
+                file.name.endsWith(".kt") -> loadKotlinSource(file, relativePath)
                 file.name.endsWith(".java") -> loadJavaSource(file, qName)
                 else -> error("invalid file extension ${file.name}")
             }
diff --git a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/TestUilts.kt b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/TestUilts.kt
new file mode 100644
index 0000000..bf96270
--- /dev/null
+++ b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/TestUilts.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.room.compiler.processing.util
+
+import java.io.File
+
+/**
+ * Returns the list of File's in the system classpath
+ *
+ * @see getSystemClasspaths
+ */
+fun getSystemClasspathFiles(): Set<File> {
+    return getSystemClasspaths().map { File(it) }.toSet()
+}
+
+/**
+ * Returns the file paths from the system class loader
+ *
+ * @see getSystemClasspathFiles
+ */
+fun getSystemClasspaths(): Set<String> {
+    val pathSeparator = System.getProperty("path.separator")!!
+    return System.getProperty("java.class.path")!!.split(pathSeparator).toSet()
+}
\ No newline at end of file
diff --git a/room/compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/GeneratedCodeMatchTest.kt b/room/compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/GeneratedCodeMatchTest.kt
index 832937d..0f9c6a9 100644
--- a/room/compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/GeneratedCodeMatchTest.kt
+++ b/room/compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/GeneratedCodeMatchTest.kt
@@ -20,7 +20,11 @@
 import com.squareup.javapoet.JavaFile
 import com.squareup.javapoet.TypeName
 import com.squareup.javapoet.TypeSpec
+import com.squareup.kotlinpoet.BOOLEAN
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.TypeSpec as KTypeSpec
 import org.junit.Test
+import org.junit.AssumptionViolatedException
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 
@@ -111,4 +115,106 @@
         )
         assertThat(result.exceptionOrNull()).hasMessageThat().contains(mismatch.toString())
     }
+
+    @Test
+    fun successfulGeneratedKotlinCodeMatch() {
+        // java environment will not generate kotlin files
+        if (runTest.toString() == "java") {
+            throw AssumptionViolatedException("javaAP won't generate kotlin code.")
+        }
+
+        val file = FileSpec.builder("foo.bar", "Baz")
+            .addType(KTypeSpec.classBuilder("Baz").build())
+            .build()
+        runTest { invocation ->
+            if (invocation.processingEnv.findTypeElement("foo.bar.Baz") == null) {
+                invocation.processingEnv.filer.write(file)
+            }
+            invocation.assertCompilationResult {
+                generatedSource(
+                    Source.kotlin("foo/bar/Baz.kt", file.toString())
+                )
+            }
+        }
+    }
+
+    @Test
+    fun missingGeneratedKotlinCode_mismatch() {
+        // java environment will not generate kotlin files
+        if (runTest.toString() == "java") {
+            throw AssumptionViolatedException("javaAP won't generate kotlin code.")
+        }
+
+        val generated = FileSpec.builder("foo.bar", "Baz")
+            .addType(
+                KTypeSpec.classBuilder("Baz")
+                    .addProperty("bar", BOOLEAN)
+                    .build()
+            )
+            .build()
+        val expected = FileSpec.builder("foo.bar", "Baz")
+            .addType(
+                KTypeSpec.classBuilder("Baz")
+                    .addProperty("foo", BOOLEAN)
+                    .build()
+            )
+            .build()
+
+        val result = runCatching {
+            runTest { invocation ->
+                if (invocation.processingEnv.findTypeElement("foo.bar.Baz") == null) {
+                    invocation.processingEnv.filer.write(generated)
+                }
+                invocation.assertCompilationResult {
+                    generatedSource(
+                        Source.kotlin("foo/bar/Baz.kt", expected.toString())
+                    )
+                }
+            }
+        }
+
+        val mismatch = SourceFileMismatch(
+            expected = Line(
+                pos = 6,
+                content = "val foo: Boolean"
+            ),
+            actual = Line(
+                pos = 6,
+                content = "val bar: Boolean"
+            )
+        )
+        assertThat(result.exceptionOrNull()).hasMessageThat().contains(mismatch.toString())
+    }
+
+    @Test
+    fun missingGeneratedKotlinCode_javaAP() {
+        if (runTest.toString() != "java") {
+            throw AssumptionViolatedException("Testing scenario for javaAP only.")
+        }
+
+        val file = FileSpec.builder("foo.bar", "Baz")
+            .addType(KTypeSpec.classBuilder("Baz").build())
+            .build()
+
+        val result = runCatching {
+            runTest { invocation ->
+                if (invocation.processingEnv.findTypeElement("foo.bar.Baz") == null) {
+                    invocation.processingEnv.filer.write(file)
+                }
+                invocation.assertCompilationResult {
+                    generatedSource(
+                        Source.kotlin("foo/bar/Baz.kt", file.toString())
+                    )
+                }
+            }
+        }
+
+        assertThat(result.exceptionOrNull())
+            .hasCauseThat()
+            .hasMessageThat()
+            .contains(
+                "Could not generate kotlin file foo/bar/Baz.kt. The annotation processing " +
+                    "environment is not set to generate Kotlin files."
+            )
+    }
 }
diff --git a/room/compiler-processing/README.md b/room/compiler-processing/README.md
new file mode 100644
index 0000000..98a7f3de
--- /dev/null
+++ b/room/compiler-processing/README.md
@@ -0,0 +1,101 @@
+# X Processing
+
+This module (room-compiler-processing) provides an abstraction over Java Annotation Processing
+(JavaAP) and Kotlin Symbol Processing (KSP) for Room's annotation processor.
+
+If you are an annotation processor author and want to add KSP support to your project (while still
+supporting Java AP), this library might be useful to get ideas or even directly use it in your
+project.
+
+**DISCLAIMER**
+This is NOT a public Jetpack library and it will NOT have any of the API guarantees that Jetpack
+Libraries provide (e.g. no semantic versioning). That being said, if it is useful for your use
+case, feel free to use it. If you include it in your project, please `jarjar` it to avoid classpath
+conflicts with Room.
+This library is still expected to be a production quality abstraction because Room relies on it.
+
+## Project Goals
+This is **not** a general purpose abstraction over JavaAP(JSR 269) and KSP. Instead, it is designed
+to support what Room needs (and only what Room needs). Despite this goal, it ended up covering most
+use cases hence the library can be used as a general purpose abstraction and it also includes a
+testing artifact (room-compiler-processing-testing).
+
+If this abstraction turns out to be useful to enough people, we are open to unbundling it
+from Room as an independent library.
+
+### Want to make changes?
+As long as it does not break Room, we are happy to expand the library to cover your use case. If you
+would like a change:
+
+* File a bug. [example](https://issuetracker.google.com/issues/182195680)
+* Once the bug is acknowledged by the Room team, send a PR.
+[example](https://github.com/androidx/androidx/pull/137)
+  * Make sure to add good tests coveraging the change in the PR. Not only it is mandatory for
+  AndroidX submissions, but will also help us ensure we don't break it.
+
+
+## How To Use It
+
+The entry API is the [XProcessingEnv](src/main/java/androidx/room/compiler/processing/XProcessingEnv.kt)
+which has the necessary API's to access types etc.
+
+To find annotated elements, you need to subclass the
+[XProcessingStep](src/main/java/androidx/room/compiler/processing/XProcessingStep.kt).
+It is very similar to the
+[BasicAnnotationProcessor](https://github.com/google/auto/blob/master/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java)
+API in [Google Auto](https://github.com/google/auto) and you can find Room's implementation
+[here](../compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt).
+
+To initialize your `XProcessingStep` implementation, you still need to create your own
+ AnnotationProcessor (JavaAP) or SymbolProcessor (KSP) implementations and tie it to the
+ `XProcessingStep`.
+* Room's [KSP processor](../compiler/src/main/kotlin/androidx/room/RoomKspProcessor.kt)
+* Room's [JavaAP processor](../compiler/src/main/kotlin/androidx/room/RoomProcessor.kt)
+
+For everything else, the API docs in the library should be sufficient and if not, feel free to file
+bugs for more explanations.
+
+## Main Classes
+### XTypeElement
+This is analogous to JavaAP's `TypeElement` or KSP's `KSClassDeclaration`.
+It can be used to get methods, fields etc declared in a class declaration.
+### XType
+This is analogous to JavaAP's `TypeMirror` or KSP's `KSType`.  For convenience, each
+`XType` has a `typeName` property that convert it to JavaPoet's `TypeName`. For types that do not
+match 1-1 between Kotlin and Java (e.g. Kotlin `Int` maps to `java.lang.Integer` or primitive `int
+`), Room will do a best effort conversion based on the information avaiable from the use site.
+### XFieldElement
+Represents a field in Java. Might be driven from a kotlin property.
+You can obtain them via `XTypeElement`.
+### XMethodElement
+Represents a Java method or Kotlin function. They can be obtained from `XTypeElement`.
+### XConstructorElement
+Similar to `XMethodElement` but only represents constructor functions.
+### XMethodType
+Represents an `XMethodElement` as member of an `XType`. Notice that when a method is resolved as
+a member of an `XType`, all available type arguments will be resolved based on the type
+declaration.
+
+## Known Caveats (a.k.a. hacks)
+XProcessing assumes it is being used to generate Java code and tries to optimize for that. Chances
+are, if you are converting a Java AP to support KSP, your annotation processor expects what
+XProcessing does.
+Caveats listed here are subject to change if/when Room supports generating Kotlin code but it is
+very likely that we'll make it an argument to XProcessing.
+
+### Suspend Methods
+For suspend methods, it will synthesize an additional `Continuation` parameter.
+
+### Properties
+XProcessing will synthesize `getter`/`setter` methods for Kotlin properties.
+
+### Overrides
+When checking if a method overrides another, XProcessing [checks its JVM declaration as well
+](https://android-review.googlesource.com/c/platform/frameworks/support/+/1564504/11/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/ResolverExt.kt#68),
+which may not be what you see in Kotlin sources but this is exactly what an Annotation Processor
+would see when used via KAPT.
+
+### Internal Modifier
+When an internal modifier is used in Kotlin code, its binary representation has a mangled name.
+XProcessing automatically checks for it and the `name` property of a method/property will include
+that conversion.
diff --git a/room/compiler-processing/build.gradle b/room/compiler-processing/build.gradle
index 0f69d5c..6f96dda 100644
--- a/room/compiler-processing/build.gradle
+++ b/room/compiler-processing/build.gradle
@@ -28,6 +28,7 @@
 dependencies {
     api(KOTLIN_STDLIB)
     api(JAVAPOET)
+    api(KOTLINPOET)
     implementation("androidx.annotation:annotation:1.1.0")
     implementation(GUAVA)
     implementation(AUTO_COMMON)
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XFiler.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XFiler.kt
index dce852c..336814e 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XFiler.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XFiler.kt
@@ -17,12 +17,17 @@
 package androidx.room.compiler.processing
 
 import com.squareup.javapoet.JavaFile
+import com.squareup.kotlinpoet.FileSpec
 
 /**
  * Code generation interface for XProcessing.
  */
 interface XFiler {
     fun write(javaFile: JavaFile)
+
+    fun write(fileSpec: FileSpec)
 }
 
 fun JavaFile.writeTo(generator: XFiler) = generator.write(this)
+
+fun FileSpec.writeTo(generator: XFiler) = generator.write(this)
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacFiler.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacFiler.kt
index 354236a..d84c8a6 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacFiler.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacFiler.kt
@@ -18,10 +18,20 @@
 
 import androidx.room.compiler.processing.XFiler
 import com.squareup.javapoet.JavaFile
-import javax.annotation.processing.Filer
+import com.squareup.kotlinpoet.FileSpec
+import javax.annotation.processing.ProcessingEnvironment
 
-internal class JavacFiler(val filer: Filer) : XFiler {
+internal class JavacFiler(val processingEnv: ProcessingEnvironment) : XFiler {
     override fun write(javaFile: JavaFile) {
-        javaFile.writeTo(filer)
+        javaFile.writeTo(processingEnv.filer)
+    }
+
+    override fun write(fileSpec: FileSpec) {
+        require(processingEnv.options.containsKey("kapt.kotlin.generated")) {
+            val filePath = fileSpec.packageName.replace('.', '/')
+            "Could not generate kotlin file $filePath/${fileSpec.name}.kt. The " +
+                "annotation processing environment is not set to generate Kotlin files."
+        }
+        fileSpec.writeTo(processingEnv.filer)
     }
 }
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt
index 2266ced..175cfec 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacProcessingEnv.kt
@@ -58,7 +58,7 @@
         JavacProcessingEnvMessager(delegate)
     }
 
-    override val filer = JavacFiler(delegate.filer)
+    override val filer = JavacFiler(delegate)
 
     override val options: Map<String, String>
         get() = delegate.options
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
index 8614181..865214f 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KSTypeExt.kt
@@ -61,7 +61,10 @@
     // TODO: https://issuetracker.google.com/issues/168639183
     val qualified = qualifiedName?.asString() ?: return ERROR_TYPE_NAME
     val jvmSignature = resolver.mapToJvmSignature(this)
-    if (jvmSignature.isNotBlank()) {
+    // https://github.com/google/ksp/commit/964e6f87a55e8ac159dbc37b4a70fc07a0b02e34
+    // jvmSignature will be nullable in alpha06
+    @Suppress("SENSELESS_COMPARISON")
+    if (jvmSignature != null && jvmSignature.isNotBlank()) {
         return jvmSignature.typeNameFromJvmSignature()
     }
     if (this is KSTypeParameter) {
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotated.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotated.kt
index fc9dbae..0e6a7aa 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotated.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotated.kt
@@ -113,10 +113,12 @@
             }
         }
 
+        /**
+         * TODO: We should be able to remove use site filters once
+         * https://github.com/google/ksp/issues/355 is fixed.
+         */
         companion object {
             val FIELD: UseSiteFilter = Impl(true, AnnotationUseSiteTarget.FIELD)
-            val PROPERTY_GETTER: UseSiteFilter = Impl(false, AnnotationUseSiteTarget.GET)
-            val PROPERTY_SETTER: UseSiteFilter = Impl(false, AnnotationUseSiteTarget.SET)
             val PROPERTY_SETTER_PARAMETER: UseSiteFilter =
                 Impl(false, AnnotationUseSiteTarget.SETPARAM)
             val METHOD_PARAMETER: UseSiteFilter = Impl(true, AnnotationUseSiteTarget.PARAM)
@@ -125,6 +127,8 @@
                     return annotation.useSiteTarget == null
                 }
             }
+            val NO_USE_SITE_OR_GETTER: UseSiteFilter = Impl(true, AnnotationUseSiteTarget.GET)
+            val NO_USE_SITE_OR_SETTER: UseSiteFilter = Impl(true, AnnotationUseSiteTarget.SET)
         }
     }
 
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFiler.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFiler.kt
index 90e08c4..0b92034 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFiler.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspFiler.kt
@@ -20,7 +20,12 @@
 import androidx.room.compiler.processing.XMessager
 import com.google.devtools.ksp.processing.CodeGenerator
 import com.google.devtools.ksp.processing.Dependencies
+import com.google.devtools.ksp.symbol.KSFile
 import com.squareup.javapoet.JavaFile
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.TypeSpec
+import java.io.OutputStream
+import javax.lang.model.element.Element
 import javax.tools.Diagnostic
 
 internal class KspFiler(
@@ -29,17 +34,56 @@
 ) : XFiler {
     override fun write(javaFile: JavaFile) {
         val originatingFiles = javaFile.typeSpec.originatingElements
-            .map {
-                check(it is KSFileAsOriginatingElement) {
-                    "Unexpected element type in originating elements. $it"
-                }
-                it.ksFile
+            .map(::originatingFileFor)
+
+        createNewFile(
+            originatingFiles = originatingFiles,
+            packageName = javaFile.packageName,
+            fileName = javaFile.typeSpec.name,
+            extensionName = "java"
+        ).use { outputStream ->
+            outputStream.bufferedWriter(Charsets.UTF_8).use {
+                javaFile.writeTo(it)
             }
+        }
+    }
+
+    override fun write(fileSpec: FileSpec) {
+        val originatingFiles = fileSpec.members
+            .filterIsInstance<TypeSpec>()
+            .flatMap { it.originatingElements }
+            .map(::originatingFileFor)
+
+        createNewFile(
+            originatingFiles = originatingFiles,
+            packageName = fileSpec.packageName,
+            fileName = fileSpec.name,
+            extensionName = "kt"
+        ).use { outputStream ->
+            outputStream.bufferedWriter(Charsets.UTF_8).use {
+                fileSpec.writeTo(it)
+            }
+        }
+    }
+
+    private fun originatingFileFor(element: Element): KSFile {
+        check(element is KSFileAsOriginatingElement) {
+            "Unexpected element type in originating elements. $element"
+        }
+        return element.ksFile
+    }
+
+    private fun createNewFile(
+        originatingFiles: List<KSFile>,
+        packageName: String,
+        fileName: String,
+        extensionName: String
+    ): OutputStream {
         val dependencies = if (originatingFiles.isEmpty()) {
             messager.printMessage(
                 Diagnostic.Kind.WARNING,
                 """
-                    No dependencies are reported for ${javaFile.typeSpec.name} which will prevent
+                    No dependencies are reported for $fileName which will prevent
                     incremental compilation. Please file a bug at:
                     https://issuetracker.google.com/issues/new?component=413107
                 """.trimIndent()
@@ -52,15 +96,11 @@
             )
         }
 
-        delegate.createNewFile(
+        return delegate.createNewFile(
             dependencies = dependencies,
-            packageName = javaFile.packageName,
-            fileName = javaFile.typeSpec.name,
-            extensionName = "java"
-        ).use { outputStream ->
-            outputStream.bufferedWriter(Charsets.UTF_8).use {
-                javaFile.writeTo(it)
-            }
-        }
+            packageName = packageName,
+            fileName = fileName,
+            extensionName = extensionName
+        )
     }
 }
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/ResolverExt.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/ResolverExt.kt
index 415dfd5..799ae1307 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/ResolverExt.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/ResolverExt.kt
@@ -113,12 +113,11 @@
     declaration: KSFunctionDeclaration
 ): String {
     return try {
-        getJvmName(declaration)
-    } catch (ignored: ClassCastException) {
-        // TODO remove this catch once that issue is fixed.
-        // workaround for https://github.com/google/ksp/issues/164
-        return declaration.simpleName.asString()
+        // https://github.com/google/ksp/commit/964e6f87a55e8ac159dbc37b4a70fc07a0b02e34
+        @Suppress("USELESS_ELVIS") // this will be nullable in alpha06
+        getJvmName(declaration) ?: declaration.simpleName.asString()
     } catch (cannotFindDeclaration: IllegalStateException) {
+        // TODO remove this catch once that issue is fixed.
         // workaround for https://github.com/google/ksp/issues/240
         return declaration.simpleName.asString()
     }
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt
index 12326a1..ca7bbdf 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt
@@ -26,8 +26,8 @@
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.ksp.KspAnnotated
 import androidx.room.compiler.processing.ksp.KspAnnotated.UseSiteFilter.Companion.NO_USE_SITE
-import androidx.room.compiler.processing.ksp.KspAnnotated.UseSiteFilter.Companion.PROPERTY_GETTER
-import androidx.room.compiler.processing.ksp.KspAnnotated.UseSiteFilter.Companion.PROPERTY_SETTER
+import androidx.room.compiler.processing.ksp.KspAnnotated.UseSiteFilter.Companion.NO_USE_SITE_OR_GETTER
+import androidx.room.compiler.processing.ksp.KspAnnotated.UseSiteFilter.Companion.NO_USE_SITE_OR_SETTER
 import androidx.room.compiler.processing.ksp.KspAnnotated.UseSiteFilter.Companion.PROPERTY_SETTER_PARAMETER
 import androidx.room.compiler.processing.ksp.KspFieldElement
 import androidx.room.compiler.processing.ksp.KspHasModifiers
@@ -107,11 +107,7 @@
         XAnnotated by KspAnnotated.create(
             env = env,
             delegate = field.declaration.getter,
-            filter = NO_USE_SITE
-        ) + KspAnnotated.create(
-            env = env,
-            delegate = field.declaration,
-            filter = PROPERTY_GETTER
+            filter = NO_USE_SITE_OR_GETTER
         ) {
         override val equalityItems: Array<out Any?> by lazy {
             arrayOf(field, "getter")
@@ -120,9 +116,8 @@
         @OptIn(KspExperimental::class)
         override val name: String by lazy {
             field.declaration.getter?.let {
-                return@lazy env.resolver.getJvmName(it)
-            }
-            computeGetterName(field.name)
+                env.resolver.getJvmName(it)
+            } ?: computeGetterName(field.name)
         }
 
         override val returnType: XType by lazy {
@@ -167,11 +162,7 @@
         XAnnotated by KspAnnotated.create(
             env = env,
             delegate = field.declaration.setter,
-            filter = NO_USE_SITE
-        ) + KspAnnotated.create(
-            env = env,
-            delegate = field.declaration,
-            filter = PROPERTY_SETTER
+            filter = NO_USE_SITE_OR_SETTER
         ) {
         override val equalityItems: Array<out Any?> by lazy {
             arrayOf(field, "setter")
@@ -180,9 +171,8 @@
         @OptIn(KspExperimental::class)
         override val name: String by lazy {
             field.declaration.setter?.let {
-                return@lazy env.resolver.getJvmName(it)
-            }
-            computeSetterName(field.name)
+                env.resolver.getJvmName(it)
+            } ?: computeSetterName(field.name)
         }
 
         override val returnType: XType by lazy {
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt
index fc97d01..f8bf777 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationBoxTest.kt
@@ -22,10 +22,14 @@
 import androidx.room.compiler.processing.testcode.JavaEnum
 import androidx.room.compiler.processing.testcode.MainAnnotation
 import androidx.room.compiler.processing.testcode.OtherAnnotation
+import androidx.room.compiler.processing.testcode.TestSuppressWarnings
 import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.compileFiles
 import androidx.room.compiler.processing.util.getField
 import androidx.room.compiler.processing.util.getMethod
 import androidx.room.compiler.processing.util.getParameter
+import androidx.room.compiler.processing.util.getSystemClasspathFiles
 import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.compiler.processing.util.runProcessorTestWithoutKsp
 import androidx.room.compiler.processing.util.typeName
@@ -34,27 +38,64 @@
 import com.squareup.javapoet.ClassName
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
+import org.junit.runners.Parameterized
 import java.util.LinkedHashMap
 
-@RunWith(JUnit4::class)
-class XAnnotationBoxTest {
+@RunWith(Parameterized::class)
+class XAnnotationBoxTest(
+    private val preCompiled: Boolean
+) {
+    private fun runTest(
+        sources: List<Source>,
+        handler: (XTestInvocation) -> Unit
+    ) {
+        if (preCompiled) {
+            val compiled = compileFiles(sources)
+            val hasKotlinSources = sources.any {
+                it is Source.KotlinSource
+            }
+            val kotlinSources = if (hasKotlinSources) {
+                listOf(
+                    Source.kotlin("placeholder.kt", "class PlaceholderKotlin")
+                )
+            } else {
+                emptyList()
+            }
+            val newSources = kotlinSources + Source.java(
+                "PlaceholderJava",
+                "public class " +
+                    "PlaceholderJava {}"
+            )
+            runProcessorTest(
+                sources = newSources,
+                handler = handler,
+                classpath = listOf(compiled) + getSystemClasspathFiles()
+            )
+        } else {
+            runProcessorTest(
+                sources = sources,
+                handler = handler
+            )
+        }
+    }
+
     @Test
     fun readSimpleAnnotationValue() {
         val source = Source.java(
             "foo.bar.Baz",
             """
             package foo.bar;
-            @SuppressWarnings({"warning1", "warning 2"})
+            import androidx.room.compiler.processing.testcode.TestSuppressWarnings;
+            @TestSuppressWarnings({"warning1", "warning 2"})
             public class Baz {
             }
             """.trimIndent()
         )
-        runProcessorTest(
+        runTest(
             sources = listOf(source)
         ) {
             val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
-            val annotationBox = element.toAnnotationBox(SuppressWarnings::class)
+            val annotationBox = element.toAnnotationBox(TestSuppressWarnings::class)
             assertThat(annotationBox).isNotNull()
             assertThat(
                 annotationBox!!.value.value
@@ -126,16 +167,17 @@
         val source = Source.kotlin(
             "Foo.kt",
             """
-            @SuppressWarnings("warning1", "warning 2")
+            import androidx.room.compiler.processing.testcode.TestSuppressWarnings
+            @TestSuppressWarnings("warning1", "warning 2")
             class Subject {
             }
             """.trimIndent()
         )
-        runProcessorTest(
+        runTest(
             sources = listOf(source)
         ) {
             val element = it.processingEnv.requireTypeElement("Subject")
-            val annotationBox = element.toAnnotationBox(SuppressWarnings::class)
+            val annotationBox = element.toAnnotationBox(TestSuppressWarnings::class)
             assertThat(annotationBox).isNotNull()
             assertThat(
                 annotationBox!!.value.value
@@ -171,16 +213,18 @@
             }
             """.trimIndent()
         )
-        runProcessorTest(
+        runTest(
             listOf(mySource)
         ) { invocation ->
             val element = invocation.processingEnv.requireTypeElement("Subject")
             element.toAnnotationBox(MainAnnotation::class)!!.let { annotation ->
                 assertThat(
-                    annotation.getAsTypeList("typeList")
+                    annotation.getAsTypeList("typeList").map {
+                        it.typeName
+                    }
                 ).containsExactly(
-                    invocation.processingEnv.requireType(String::class.typeName()),
-                    invocation.processingEnv.requireType(Int::class.typeName())
+                    String::class.typeName(),
+                    Int::class.typeName()
                 )
                 assertThat(
                     annotation.getAsType("singleType")
@@ -193,12 +237,17 @@
                     .let { other ->
                         assertThat(other.value.value).isEqualTo("other single")
                     }
-                annotation.getAsAnnotationBoxArray<OtherAnnotation>("otherAnnotationArray")
-                    .let { boxArray ->
-                        assertThat(boxArray).hasLength(2)
-                        assertThat(boxArray[0].value.value).isEqualTo("other list 1")
-                        assertThat(boxArray[1].value.value).isEqualTo("other list 2")
-                    }
+                if (invocation.isKsp) {
+                    // TODO fix when KSP support them
+                    //  https://github.com/google/ksp/issues/356
+                } else {
+                    annotation.getAsAnnotationBoxArray<OtherAnnotation>("otherAnnotationArray")
+                        .let { boxArray ->
+                            assertThat(boxArray).hasLength(2)
+                            assertThat(boxArray[0].value.value).isEqualTo("other list 1")
+                            assertThat(boxArray[1].value.value).isEqualTo("other list 2")
+                        }
+                }
             }
         }
     }
@@ -214,7 +263,7 @@
             }
             """.trimIndent()
         )
-        runProcessorTest(
+        runTest(
             sources = listOf(src)
         ) { invocation ->
             val subject = invocation.processingEnv.requireTypeElement("Subject")
@@ -233,19 +282,20 @@
             "Foo.kt",
             """
             import androidx.room.compiler.processing.testcode.OtherAnnotation
+            import androidx.room.compiler.processing.testcode.TestSuppressWarnings
             class Subject {
-                @SuppressWarnings("onProp1")
+                @TestSuppressWarnings("onProp1")
                 var prop1:Int = TODO()
 
-                @get:SuppressWarnings("onGetter2")
-                @set:SuppressWarnings("onSetter2")
-                @field:SuppressWarnings("onField2")
-                @setparam:SuppressWarnings("onSetterParam2")
+                @get:TestSuppressWarnings("onGetter2")
+                @set:TestSuppressWarnings("onSetter2")
+                @field:TestSuppressWarnings("onField2")
+                @setparam:TestSuppressWarnings("onSetterParam2")
                 var prop2:Int = TODO()
 
-                @get:SuppressWarnings("onGetter3")
-                @set:SuppressWarnings("onSetter3")
-                @setparam:SuppressWarnings("onSetterParam3")
+                @get:TestSuppressWarnings("onGetter3")
+                @set:TestSuppressWarnings("onSetter3")
+                @setparam:TestSuppressWarnings("onSetterParam3")
                 var prop3:Int
                     @OtherAnnotation("_onGetter3")
                     get() = 3
@@ -255,7 +305,7 @@
             }
             """.trimIndent()
         )
-        runProcessorTest(sources = listOf(src)) { invocation ->
+        runTest(sources = listOf(src)) { invocation ->
             val subject = invocation.processingEnv.requireTypeElement("Subject")
 
             subject.getField("prop1").assertHasSuppressWithValue("onProp1")
@@ -296,17 +346,18 @@
             "Foo.kt",
             """
             import androidx.room.compiler.processing.testcode.OtherAnnotation
+            import androidx.room.compiler.processing.testcode.TestSuppressWarnings
             class Subject {
                 fun noAnnotations(x:Int): Unit = TODO()
-                @SuppressWarnings("onMethod")
+                @TestSuppressWarnings("onMethod")
                 fun methodAnnotation(
-                    @SuppressWarnings("onParam") annotated:Int,
+                    @TestSuppressWarnings("onParam") annotated:Int,
                     notAnnotated:Int
                 ): Unit = TODO()
             }
             """.trimIndent()
         )
-        runProcessorTest(sources = listOf(src)) { invocation ->
+        runTest(sources = listOf(src)) { invocation ->
             val subject = invocation.processingEnv.requireTypeElement("Subject")
             subject.getMethod("noAnnotations").let { method ->
                 method.assertDoesNotHaveAnnotation()
@@ -325,17 +376,18 @@
         val src = Source.kotlin(
             "Foo.kt",
             """
-            @SuppressWarnings("onClass")
+            import androidx.room.compiler.processing.testcode.TestSuppressWarnings
+            @TestSuppressWarnings("onClass")
             data class Subject(
-                @field:SuppressWarnings("onField")
-                @param:SuppressWarnings("onConstructorParam")
-                @get:SuppressWarnings("onGetter")
-                @set:SuppressWarnings("onSetter")
+                @field:TestSuppressWarnings("onField")
+                @param:TestSuppressWarnings("onConstructorParam")
+                @get:TestSuppressWarnings("onGetter")
+                @set:TestSuppressWarnings("onSetter")
                 var x:Int
             )
             """.trimIndent()
         )
-        runProcessorTest(sources = listOf(src)) { invocation ->
+        runTest(sources = listOf(src)) { invocation ->
             val subject = invocation.processingEnv.requireTypeElement("Subject")
             subject.assertHasSuppressWithValue("onClass")
             assertThat(subject.getConstructors()).hasSize(1)
@@ -365,7 +417,7 @@
             class JavaClass {}
             """.trimIndent()
         )
-        runProcessorTest(sources = listOf(kotlinSrc, javaSrc)) { invocation ->
+        runTest(sources = listOf(kotlinSrc, javaSrc)) { invocation ->
             listOf("KotlinClass", "JavaClass")
                 .map {
                     invocation.processingEnv.requireTypeElement(it)
@@ -441,7 +493,7 @@
             }
             """.trimIndent()
         )
-        runProcessorTest(
+        runTest(
             sources = listOf(javaSrc, kotlinSrc)
         ) { invocation ->
             arrayOf("JavaSubject", "KotlinSubject").map {
@@ -461,15 +513,15 @@
 
     // helper function to read what we need
     private fun XAnnotated.getSuppressValues(): Array<String>? {
-        return this.toAnnotationBox(SuppressWarnings::class)?.value?.value
+        return this.toAnnotationBox(TestSuppressWarnings::class)?.value?.value
     }
 
     private fun XAnnotated.assertHasSuppressWithValue(vararg expected: String) {
         assertWithMessage("has suppress annotation $this")
-            .that(this.hasAnnotation(SuppressWarnings::class))
+            .that(this.hasAnnotation(TestSuppressWarnings::class))
             .isTrue()
         assertWithMessage("$this")
-            .that(this.hasAnnotationWithPackage(SuppressWarnings::class.java.packageName))
+            .that(this.hasAnnotationWithPackage(TestSuppressWarnings::class.java.packageName))
             .isTrue()
         assertWithMessage("$this")
             .that(getSuppressValues())
@@ -478,10 +530,10 @@
 
     private fun XAnnotated.assertDoesNotHaveAnnotation() {
         assertWithMessage("$this")
-            .that(this.hasAnnotation(SuppressWarnings::class))
+            .that(this.hasAnnotation(TestSuppressWarnings::class))
             .isFalse()
         assertWithMessage("$this")
-            .that(this.hasAnnotationWithPackage(SuppressWarnings::class.java.packageName))
+            .that(this.hasAnnotationWithPackage(TestSuppressWarnings::class.java.packageName))
             .isFalse()
         assertWithMessage("$this")
             .that(this.getSuppressValues())
@@ -491,4 +543,10 @@
     private fun XAnnotated.getOtherAnnotationValue(): String? {
         return this.toAnnotationBox(OtherAnnotation::class)?.value?.value
     }
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "preCompiled_{0}")
+        fun params() = arrayOf(false, true)
+    }
 }
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/testcode/TestSuppressWarnings.java b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/testcode/TestSuppressWarnings.java
new file mode 100644
index 0000000..f2575ad
--- /dev/null
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/testcode/TestSuppressWarnings.java
@@ -0,0 +1,39 @@
+/*
+ * 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.room.compiler.processing.testcode;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.MODULE;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
+@Retention(RetentionPolicy.CLASS)
+/**
+ * Just like the {@link SuppressWarnings} annotation except with class retention so that we can
+ * test it in compiled code as well.
+  */
+public @interface TestSuppressWarnings {
+    String[] value();
+}
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
index 51ddc28..d857001 100644
--- a/room/compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
@@ -21,6 +21,7 @@
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
 import androidx.room.compiler.processing.util.compileFiles
+import androidx.room.compiler.processing.util.getSystemClasspathFiles
 import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.RoomTypeNames
 import androidx.room.testing.context
@@ -29,7 +30,6 @@
 import androidx.room.vo.Warning
 import com.squareup.javapoet.TypeName
 import createVerifierFromEntitiesAndViews
-import getSystemClasspathFiles
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.MatcherAssert.assertThat
 import org.junit.Test
diff --git a/room/compiler/src/test/kotlin/androidx/room/testing/test_util.kt b/room/compiler/src/test/kotlin/androidx/room/testing/test_util.kt
index a801b40..d38af3f 100644
--- a/room/compiler/src/test/kotlin/androidx/room/testing/test_util.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/testing/test_util.kt
@@ -21,6 +21,7 @@
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.getSystemClasspathFiles
 import androidx.room.ext.GuavaUtilConcurrentTypeNames
 import androidx.room.ext.KotlinTypeNames
 import androidx.room.ext.LifecyclesTypeNames
@@ -259,7 +260,7 @@
 
 fun loadTestSource(fileName: String, qName: String): Source {
     val contents = File("src/test/data/$fileName")
-    return Source.load(contents, qName)
+    return Source.load(contents, qName, fileName)
 }
 
 fun createVerifierFromEntitiesAndViews(invocation: TestInvocation): DatabaseVerifier {
@@ -337,11 +338,6 @@
     return getSystemClasspathFiles() + lib
 }
 
-fun getSystemClasspathFiles(): Set<File> {
-    val pathSeparator = System.getProperty("path.separator")!!
-    return System.getProperty("java.class.path")!!.split(pathSeparator).map { File(it) }.toSet()
-}
-
 fun String.toJFO(qName: String): JavaFileObject = JavaFileObjects.forSourceLines(qName, this)
 
 fun Collection<JavaFileObject>.toSources() = map(Source::fromJavaFileObject)
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/FlowQueryTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/FlowQueryTest.kt
index 3ee8bf8..bccdce2 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/FlowQueryTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/FlowQueryTest.kt
@@ -17,6 +17,7 @@
 package androidx.room.integration.kotlintestapp.test
 
 import androidx.room.integration.kotlintestapp.vo.Book
+import androidx.room.withTransaction
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
@@ -28,6 +29,7 @@
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.flow.buffer
 import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.produceIn
 import kotlinx.coroutines.flow.take
 import kotlinx.coroutines.runBlocking
@@ -64,6 +66,31 @@
     }
 
     @Test
+    fun collectBooks_first() = runBlocking {
+        booksDao.addAuthors(TestUtil.AUTHOR_1)
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1, TestUtil.BOOK_2)
+
+        val result = booksDao.getBooksFlow().first()
+        assertThat(result)
+            .isEqualTo(listOf(TestUtil.BOOK_1, TestUtil.BOOK_2))
+    }
+
+    @Test
+    fun collectBooks_first_inTransaction() = runBlocking {
+        booksDao.addAuthors(TestUtil.AUTHOR_1)
+        booksDao.addPublishers(TestUtil.PUBLISHER)
+        booksDao.addBooks(TestUtil.BOOK_1)
+
+        database.withTransaction {
+            booksDao.insertBookSuspend(TestUtil.BOOK_2)
+            val result = booksDao.getBooksFlow().first()
+            assertThat(result)
+                .isEqualTo(listOf(TestUtil.BOOK_1, TestUtil.BOOK_2))
+        }
+    }
+
+    @Test
     fun collectBooks_async() = runBlocking {
         booksDao.addAuthors(TestUtil.AUTHOR_1)
         booksDao.addPublishers(TestUtil.PUBLISHER)
diff --git a/room/ktx/src/main/java/androidx/room/CoroutinesRoom.kt b/room/ktx/src/main/java/androidx/room/CoroutinesRoom.kt
index 4d5ecce..a72e89b 100644
--- a/room/ktx/src/main/java/androidx/room/CoroutinesRoom.kt
+++ b/room/ktx/src/main/java/androidx/room/CoroutinesRoom.kt
@@ -111,7 +111,8 @@
             }
             observerChannel.offer(Unit) // Initial signal to perform first query.
             val flowContext = coroutineContext
-            val queryContext = if (inTransaction) db.transactionDispatcher else db.queryDispatcher
+            val queryContext = coroutineContext[TransactionElement]?.transactionDispatcher
+                ?: if (inTransaction) db.transactionDispatcher else db.queryDispatcher
             withContext(queryContext) {
                 db.invalidationTracker.addObserver(observer)
                 try {
diff --git a/room/runtime/build.gradle b/room/runtime/build.gradle
index 31b6246..53f0fc8 100644
--- a/room/runtime/build.gradle
+++ b/room/runtime/build.gradle
@@ -42,7 +42,7 @@
     implementation("androidx.arch.core:core-runtime:2.0.1")
     compileOnly("androidx.paging:paging-common:2.0.0")
     compileOnly("androidx.lifecycle:lifecycle-livedata-core:2.0.0")
-    implementation("androidx.annotation:annotation-experimental:1.1.0-beta01")
+    implementation("androidx.annotation:annotation-experimental:1.1.0-rc01")
     compileOnly KOTLIN_STDLIB // Due to :annotation-experimental
 
     testImplementation("androidx.arch.core:core-testing:2.0.1")
diff --git a/room/runtime/src/androidTest/java/androidx/room/AutoCloserTest.kt b/room/runtime/src/androidTest/java/androidx/room/AutoCloserTest.kt
index 8deb8c2..474c81d 100644
--- a/room/runtime/src/androidTest/java/androidx/room/AutoCloserTest.kt
+++ b/room/runtime/src/androidTest/java/androidx/room/AutoCloserTest.kt
@@ -23,6 +23,7 @@
 import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.MediumTest
 import androidx.testutils.assertThrows
 import com.google.common.truth.Truth.assertThat
@@ -130,6 +131,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 182343970)
     public fun dbNotClosedWithRefCountIncremented() {
         autoCloser.incrementCountAndEnsureDbIsOpen()
 
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/graphics/PaletteActivity.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/graphics/PaletteActivity.java
index 8e67c3a..ad993d9 100644
--- a/samples/Support7Demos/src/main/java/com/example/android/supportv7/graphics/PaletteActivity.java
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/graphics/PaletteActivity.java
@@ -178,7 +178,6 @@
             @SuppressLint("RestrictedApi")
             public PhotosCursorAdapter(Context context, Cursor c) {
                 super(context, R.layout.palette_list_item, c, false);
-                mContext = context;
             }
 
             /**
@@ -247,4 +246,4 @@
 
     }
 
-}
\ No newline at end of file
+}
diff --git a/settings.gradle b/settings.gradle
index 77a3e7d..a72757e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -263,10 +263,13 @@
 includeProject(":compose:integration-tests:docs-snippets", "compose/integration-tests/docs-snippets", [BuildType.COMPOSE])
 includeProject(":compose:integration-tests:macrobenchmark", "compose/integration-tests/macrobenchmark", [BuildType.COMPOSE])
 includeProject(":compose:integration-tests:macrobenchmark-target", "compose/integration-tests/macrobenchmark-target", [BuildType.COMPOSE])
-includeProject(":compose:internal-lint-checks", "compose/internal-lint-checks", [BuildType.COMPOSE])
+includeProject(":compose:lint", "compose/lint", [BuildType.COMPOSE])
+includeProject(":compose:lint:internal-lint-checks", "compose/lint/internal-lint-checks", [BuildType.COMPOSE])
+includeProject(":compose:lint:common", "compose/lint/common", [BuildType.COMPOSE])
 includeProject(":compose:material", "compose/material", [BuildType.COMPOSE])
 includeProject(":compose:material:material", "compose/material/material", [BuildType.COMPOSE])
 includeProject(":compose:material:material-benchmark", "compose/material/material/benchmark", [BuildType.COMPOSE])
+includeProject(":compose:material:material-lint", "compose/material/material-lint", [BuildType.COMPOSE])
 includeProject(":compose:material:material-icons-core", "compose/material/material-icons-core", [BuildType.COMPOSE])
 includeProject(":compose:material:material-icons-core:material-icons-core-samples", "compose/material/material-icons-core/samples", [BuildType.COMPOSE])
 includeProject(":compose:material:material-icons-extended", "compose/material/material-icons-extended", [BuildType.COMPOSE])
@@ -274,7 +277,6 @@
 includeProject(":compose:material:material:icons:generator", "compose/material/material/icons/generator", [BuildType.COMPOSE])
 includeProject(":compose:material:material:integration-tests:material-demos", "compose/material/material/integration-tests/material-demos", [BuildType.COMPOSE])
 includeProject(":compose:material:material:integration-tests:material-catalog", "compose/material/material/integration-tests/material-catalog", [BuildType.COMPOSE])
-includeProject(":compose:material:material:integration-tests:material-studies", "compose/material/material/integration-tests/material-studies", [BuildType.COMPOSE])
 includeProject(":compose:material:material:material-samples", "compose/material/material/samples", [BuildType.COMPOSE])
 includeProject(":compose:runtime", "compose/runtime", [BuildType.COMPOSE])
 includeProject(":compose:runtime:runtime", "compose/runtime/runtime", [BuildType.COMPOSE])
@@ -305,6 +307,8 @@
 includeProject(":compose:ui:ui-test", "compose/ui/ui-test", [BuildType.COMPOSE])
 includeProject(":compose:ui:ui-test-font", "compose/ui/ui-test-font", [BuildType.COMPOSE])
 includeProject(":compose:ui:ui-test-junit4", "compose/ui/ui-test-junit4", [BuildType.COMPOSE])
+includeProject(":compose:ui:ui-test-manifest", "compose/ui/ui-test-manifest", [BuildType.COMPOSE])
+includeProject(":compose:ui:ui-test-manifest:integration-tests:testapp", "compose/ui/ui-test-manifest/integration-tests/testapp", [BuildType.COMPOSE])
 includeProject(":compose:ui:ui-text", "compose/ui/ui-text", [BuildType.COMPOSE])
 includeProject(":compose:ui:ui-text:ui-text-benchmark", "compose/ui/ui-text/benchmark", [BuildType.COMPOSE])
 includeProject(":compose:ui:ui-text:ui-text-samples", "compose/ui/ui-text/samples", [BuildType.COMPOSE])
@@ -322,7 +326,7 @@
 includeProject(":contentpager:contentpager", "contentpager/contentpager", [BuildType.MAIN])
 includeProject(":coordinatorlayout:coordinatorlayout", "coordinatorlayout/coordinatorlayout", [BuildType.MAIN])
 includeProject(":core-role", "core/core-role", [BuildType.MAIN])
-includeProject(":core:core", "core/core", [BuildType.MAIN])
+includeProject(":core:core", "core/core", [BuildType.MAIN, BuildType.MEDIA])
 includeProject(":core:core-animation", "core/core-animation", [BuildType.MAIN])
 includeProject(":core:core-animation-integration-tests:testapp", "core/core-animation-integration-tests/testapp", [BuildType.MAIN])
 includeProject(":core:core-animation-testing", "core/core-animation-testing", [BuildType.MAIN])
@@ -357,7 +361,8 @@
 includeProject(":emoji2:emoji2", "emoji2/emoji2", [BuildType.MAIN])
 includeProject(":emoji2:emoji2-bundled", "emoji2/emoji2-bundled", [BuildType.MAIN])
 includeProject(":emoji2:emoji2-views", "emoji2/emoji2-views", [BuildType.MAIN])
-includeProject(":emoji2:emoji2-views-core", "emoji2/emoji2-views-core", [BuildType.MAIN])
+includeProject(":emoji2:emoji2-views-helper", "emoji2/emoji2-views-helper", [BuildType.MAIN])
+includeProject(":emoji2:emoji2-benchmark", "emoji2/emoji2-benchmark", [BuildType.MAIN])
 includeProject(":enterprise-feedback", "enterprise/feedback", [BuildType.MAIN])
 includeProject(":enterprise-feedback-testing", "enterprise/feedback/testing", [BuildType.MAIN])
 includeProject(":exifinterface:exifinterface", "exifinterface/exifinterface", [BuildType.MAIN])
@@ -568,7 +573,6 @@
 includeProject(":wear:wear-phone-interactions", "wear/wear-phone-interactions", [BuildType.MAIN, BuildType.WEAR])
 includeProject(":wear:wear-remote-interactions", "wear/wear-remote-interactions", [BuildType.MAIN, BuildType.WEAR])
 includeProject(":wear:wear-tiles", "wear/wear-tiles", [BuildType.MAIN, BuildType.WEAR])
-includeProject(":wear:wear-tiles-data", "wear/wear-tiles-data", [BuildType.MAIN, BuildType.WEAR])
 includeProject(":wear:wear-tiles-proto", "wear/wear-tiles-proto", [BuildType.MAIN, BuildType.WEAR])
 includeProject(":wear:wear-tiles-renderer", "wear/wear-tiles-renderer", [BuildType.MAIN, BuildType.WEAR])
 includeProject(":wear:wear-watchface", "wear/wear-watchface", [BuildType.MAIN, BuildType.WEAR])
@@ -646,7 +650,7 @@
 includeProject(":internal-testutils-navigation", "testutils/testutils-navigation", [BuildType.MAIN, BuildType.COMPOSE, BuildType.FLAN])
 includeProject(":internal-testutils-paging", "testutils/testutils-paging", [BuildType.MAIN, BuildType.COMPOSE])
 includeProject(":internal-testutils-gradle-plugin", "testutils/testutils-gradle-plugin", [BuildType.MAIN, BuildType.FLAN])
-includeProject(":internal-testutils-mockito", "testutils/testutils-mockito", [BuildType.MAIN])
+includeProject(":internal-testutils-mockito", "testutils/testutils-mockito", [BuildType.MAIN, BuildType.MEDIA])
 
 /////////////////////////////
 //
diff --git a/slidingpanelayout/OWNERS b/slidingpanelayout/OWNERS
new file mode 100644
index 0000000..8eb6c00
--- /dev/null
+++ b/slidingpanelayout/OWNERS
@@ -0,0 +1,2 @@
[email protected]
[email protected]
diff --git a/slidingpanelayout/slidingpanelayout/build.gradle b/slidingpanelayout/slidingpanelayout/build.gradle
index 9801061..86340b6 100644
--- a/slidingpanelayout/slidingpanelayout/build.gradle
+++ b/slidingpanelayout/slidingpanelayout/build.gradle
@@ -11,7 +11,7 @@
 dependencies {
     api("androidx.annotation:annotation:1.1.0")
     implementation("androidx.core:core:1.1.0")
-    api(project(":customview:customview"))
+    api("androidx.customview:customview:1.1.0")
     implementation(project(":window:window"))
 
     androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
@@ -27,5 +27,5 @@
     publish = Publish.SNAPSHOT_AND_RELEASE
     mavenGroup = LibraryGroups.SLIDINGPANELAYOUT
     inceptionYear = "2018"
-    description = "The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later."
+    description = "SlidingPaneLayout offers a responsive, two pane layout that automatically switches between overlapping panes on smaller devices to a side by side view on larger devices."
 }
diff --git a/startup/integration-tests/first-library/src/main/AndroidManifest.xml b/startup/integration-tests/first-library/src/main/AndroidManifest.xml
index c1360e8..dc4bfdc 100644
--- a/startup/integration-tests/first-library/src/main/AndroidManifest.xml
+++ b/startup/integration-tests/first-library/src/main/AndroidManifest.xml
@@ -29,14 +29,13 @@
             <meta-data
                 android:name="androidx.startup.first_library.WorkManagerInitializer"
                 android:value="@string/androidx_startup" />
-        </provider>
 
-        <!-- Remove provider for WorkManager on-demand initialization -->
-        <provider
-            android:name="androidx.work.impl.WorkManagerInitializer"
-            android:authorities="${applicationId}.workmanager-init"
-            android:exported="false"
-            tools:node="remove" />
+            <!-- Remove initializer for WorkManager on-demand initialization -->
+            <meta-data
+                android:name="androidx.work.impl.WorkManagerInitializer"
+                android:value="@string/androidx_startup"
+                tools:node="remove" />
+        </provider>
 
     </application>
 
diff --git a/test/screenshot/lint-baseline.xml b/test/screenshot/lint-baseline.xml
deleted file mode 100644
index 8f1aa4b..0000000
--- a/test/screenshot/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-</issues>
diff --git a/test/screenshot/src/main/java/androidx/test/screenshot/ScreenshotTestRule.kt b/test/screenshot/src/main/java/androidx/test/screenshot/ScreenshotTestRule.kt
index 7adf777..dcc63d34 100644
--- a/test/screenshot/src/main/java/androidx/test/screenshot/ScreenshotTestRule.kt
+++ b/test/screenshot/src/main/java/androidx/test/screenshot/ScreenshotTestRule.kt
@@ -21,7 +21,6 @@
 import android.graphics.BitmapFactory
 import android.os.Build
 import android.os.Bundle
-import androidx.core.os.BuildCompat
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.screenshot.matchers.BitmapMatcher
 import androidx.test.screenshot.matchers.MSSIMMatcher
@@ -122,7 +121,7 @@
             Assume.assumeTrue("Requires Cuttlefish", Build.MODEL.contains("Cuttlefish"))
             Assume.assumeTrue(
                 "Requires SDK 29.",
-                Build.VERSION.SDK_INT == 29 && !BuildCompat.isAtLeastR()
+                Build.VERSION.SDK_INT == 29
             )
             base.evaluate()
         }
diff --git a/testutils/testutils-common/lint-baseline.xml b/testutils/testutils-common/lint-baseline.xml
deleted file mode 100644
index 297ae16..0000000
--- a/testutils/testutils-common/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" version="4.2.0-beta02">
-
-</issues>
diff --git a/testutils/testutils-ktx/lint-baseline.xml b/testutils/testutils-ktx/lint-baseline.xml
deleted file mode 100644
index 297ae16..0000000
--- a/testutils/testutils-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" version="4.2.0-beta02">
-
-</issues>
diff --git a/testutils/testutils-navigation/lint-baseline.xml b/testutils/testutils-navigation/lint-baseline.xml
deleted file mode 100644
index 8f1aa4b..0000000
--- a/testutils/testutils-navigation/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-</issues>
diff --git a/testutils/testutils-paging/lint-baseline.xml b/testutils/testutils-paging/lint-baseline.xml
deleted file mode 100644
index 297ae16..0000000
--- a/testutils/testutils-paging/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" version="4.2.0-beta02">
-
-</issues>
diff --git a/testutils/testutils-runtime/lint-baseline.xml b/testutils/testutils-runtime/lint-baseline.xml
index 135f9c2..0b60048 100644
--- a/testutils/testutils-runtime/lint-baseline.xml
+++ b/testutils/testutils-runtime/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
+<issues format="5" by="lint 4.2.0-beta04" client="gradle" variant="debug" version="4.2.0-beta04">
 
     <issue
         id="BanUncheckedReflection"
diff --git a/testutils/testutils-runtime/src/main/java/androidx/fragment/app/StrictFragment.kt b/testutils/testutils-runtime/src/main/java/androidx/fragment/app/StrictFragment.kt
index e52994e..fe1c603 100644
--- a/testutils/testutils-runtime/src/main/java/androidx/fragment/app/StrictFragment.kt
+++ b/testutils/testutils-runtime/src/main/java/androidx/fragment/app/StrictFragment.kt
@@ -103,6 +103,7 @@
         checkState("onAttach", State.DETACHED)
         currentState = State.ATTACHED
         onStateChanged(State.DETACHED)
+        @Suppress("Deprecation") // We're not setting retainInstance, just supporting it
         if (retainInstance && calledOnCreate) {
             // We were created previously
             currentState = State.CREATED
@@ -124,6 +125,7 @@
     }
 
     override fun onActivityCreated(savedInstanceState: Bundle?) {
+        @Suppress("Deprecation") // we're just calling the superclass method with the same name
         super.onActivityCreated(savedInstanceState)
         checkActivityNotDestroyed()
         calledOnActivityCreated = true
diff --git a/text/text/lint-baseline.xml b/text/text/lint-baseline.xml
index 3022dcc..0a690c9 100644
--- a/text/text/lint-baseline.xml
+++ b/text/text/lint-baseline.xml
@@ -1,81 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableLambdaParameterPosition&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnknownIssueId"
-        message="Unknown issue id &quot;ComposableNaming&quot;">
-        <location
-            file="build.gradle"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 28, the call containing class androidx.compose.ui.text.android.style.FontSpan is not annotated with @RequiresApi(x) where x is at least 28. Either annotate the containing class with at least @RequiresApi(28) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(28)."
-        errorLine1="            oldTypeface.weight"
-        errorLine2="                        ~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/text/android/style/FontSpan.kt"
-            line="46"
-            column="25"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 28, the call containing class androidx.compose.ui.text.android.style.FontWeightStyleSpan is not annotated with @RequiresApi(x) where x is at least 28. Either annotate the containing class with at least @RequiresApi(28) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(28)."
-        errorLine1="            (weight != 0 &amp;&amp; weight != oldTypeface?.weight)"
-        errorLine2="                                                   ~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/text/android/style/FontWeightStyleSpan.kt"
-            line="67"
-            column="52"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 28, the call containing class androidx.compose.ui.text.android.style.FontWeightStyleSpan is not annotated with @RequiresApi(x) where x is at least 28. Either annotate the containing class with at least @RequiresApi(28) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(28)."
-        errorLine1="            oldTypeface?.weight ?: FontStyle.FONT_WEIGHT_NORMAL"
-        errorLine2="                         ~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/text/android/style/FontWeightStyleSpan.kt"
-            line="79"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 28, the call containing class androidx.compose.ui.text.android.style.FontWeightStyleSpan is not annotated with @RequiresApi(x) where x is at least 28. Either annotate the containing class with at least @RequiresApi(28) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(28)."
-        errorLine1="        textPaint.typeface = Typeface.create(oldTypeface, newWeight, newItalic)"
-        errorLine2="                                      ~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/text/android/style/FontWeightStyleSpan.kt"
-            line="88"
-            column="39"/>
-    </issue>
-
-    <issue
-        id="UnsafeNewApiCall"
-        message="This call is to a method from API 28, the call containing class androidx.compose.ui.text.android.style.FontWeightStyleSpan is not annotated with @RequiresApi(x) where x is at least 28. Either annotate the containing class with at least @RequiresApi(28) or move the call to a static method in a wrapper class annotated with at least @RequiresApi(28)."
-        errorLine1="        textPaint.typeface = Typeface.create(oldTypeface, newWeight, newItalic)"
-        errorLine2="                                      ~~~~~~">
-        <location
-            file="src/main/java/androidx/compose/ui/text/android/style/FontWeightStyleSpan.kt"
-            line="88"
-            column="39"/>
-    </issue>
+<issues format="5" by="lint 4.2.0-beta04" client="gradle" variant="debug" version="4.2.0-beta04">
 
     <issue
         id="UnsafeNewApiCall"
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.kt b/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.kt
index 35dc84c..0480714 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/LayoutIntrinsics.kt
@@ -98,7 +98,12 @@
         end = iterator.next()
     }
 
-    return longestWordCandidates.map { pair ->
-        Layout.getDesiredWidth(text, pair.first, pair.second, paint)
-    }.maxOrNull() ?: 0f
+    var minWidth = 0f
+
+    longestWordCandidates.forEach { (start, end) ->
+        val width = Layout.getDesiredWidth(text, start, end, paint)
+        minWidth = maxOf(minWidth, width)
+    }
+
+    return minWidth
 }
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/TempListUtils.kt b/text/text/src/main/java/androidx/compose/ui/text/android/TempListUtils.kt
new file mode 100644
index 0000000..2de2410
--- /dev/null
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/TempListUtils.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.ui.text.android
+
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.contract
+
+// TODO: remove these when we can add new APIs to ui-util outside of beta cycle
+
+/**
+ * Iterates through a [List] using the index and calls [action] for each item.
+ * This does not allocate an iterator like [Iterable.forEach].
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T> List<T>.fastForEach(action: (T) -> Unit) {
+    contract { callsInPlace(action) }
+    for (index in indices) {
+        val item = get(index)
+        action(item)
+    }
+}
+
+/**
+ * Applies the given [transform] function to each element of the original collection
+ * and appends the results to the given [destination].
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T, R, C : MutableCollection<in R>> List<T>.fastMapTo(
+    destination: C,
+    transform: (T) -> R
+): C {
+    contract { callsInPlace(transform) }
+    fastForEach { item ->
+        destination.add(transform(item))
+    }
+    return destination
+}
+
+/**
+ * Returns a list containing the results of applying the given [transform] function
+ * to each pair of two adjacent elements in this collection.
+ *
+ * The returned list is empty if this collection contains less than two elements.
+ */
+@OptIn(ExperimentalContracts::class)
+internal inline fun <T, R> List<T>.fastZipWithNext(transform: (T, T) -> R): List<R> {
+    contract { callsInPlace(transform) }
+    if (size == 0 || size == 1) return emptyList()
+    val result = mutableListOf<R>()
+    var current = get(0)
+    // `until` as we don't want to invoke this for the last element, since that won't have a `next`
+    for (i in 0 until lastIndex) {
+        val next = get(i + 1)
+        result.add(transform(current, next))
+        current = next
+    }
+    return result
+}
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.kt b/text/text/src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.kt
index d9454e2..ebaba1e 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/animation/SegmentBreaker.kt
@@ -20,9 +20,12 @@
 import androidx.compose.ui.text.android.CharSequenceCharacterIterator
 import androidx.compose.ui.text.android.InternalPlatformTextApi
 import androidx.compose.ui.text.android.LayoutHelper
+import androidx.compose.ui.text.android.fastForEach
+import androidx.compose.ui.text.android.fastZipWithNext
 import androidx.compose.ui.text.android.getLineForOffset
 import java.text.BreakIterator
 import java.util.Locale
+import java.util.TreeSet
 import kotlin.math.ceil
 import kotlin.math.max
 import kotlin.math.min
@@ -59,7 +62,10 @@
         val text = layoutHelper.layout.text
         val words = breakWithBreakIterator(text, BreakIterator.getLineInstance(Locale.getDefault()))
 
-        val set = words.toSortedSet()
+        val set = TreeSet<Int>().apply {
+            words.fastForEach { add(it) }
+        }
+
         for (paraIndex in 0 until layoutHelper.paragraphCount) {
             val bidi = layoutHelper.analyzeBidi(paraIndex) ?: continue
             val paragraphStart = layoutHelper.getParagraphStart(paraIndex)
@@ -217,7 +223,7 @@
     ): List<Segment> {
         val layout = layoutHelper.layout
         val wsWidth = ceil(layout.paint.measureText(" ")).toInt()
-        return breakOffsets(layoutHelper, SegmentType.Word).zipWithNext { start, end ->
+        return breakOffsets(layoutHelper, SegmentType.Word).fastZipWithNext { start, end ->
             val lineNo = layout.getLineForOffset(start, false /* downstream */)
             val paraRTL = layout.getParagraphDirection(lineNo) == Layout.DIR_RIGHT_TO_LEFT
             val runRtl = layout.isRtlCharAt(start) // no bidi transition inside segment
@@ -266,7 +272,7 @@
         dropSpaces: Boolean
     ): List<Segment> {
         val res = mutableListOf<Segment>()
-        breakOffsets(layoutHelper, SegmentType.Character).zipWithNext lambda@{ start, end ->
+        breakOffsets(layoutHelper, SegmentType.Character).fastZipWithNext lambda@{ start, end ->
             val layout = layoutHelper.layout
 
             if (dropSpaces && end == start + 1 &&
diff --git a/tracing/tracing-ktx/lint-baseline.xml b/tracing/tracing-ktx/lint-baseline.xml
deleted file mode 100644
index 8f1aa4b..0000000
--- a/tracing/tracing-ktx/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-</issues>
diff --git a/tracing/tracing/lint-baseline.xml b/tracing/tracing/lint-baseline.xml
index 07252f3..3e52b9c 100644
--- a/tracing/tracing/lint-baseline.xml
+++ b/tracing/tracing/lint-baseline.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
+<issues format="5" by="lint 4.2.0-beta04" client="gradle" variant="debug" version="4.2.0-beta04">
 
     <issue
         id="UnsafeNewApiCall"
diff --git a/ui/ui-animation-tooling-internal/lint-baseline.xml b/ui/ui-animation-tooling-internal/lint-baseline.xml
deleted file mode 100644
index 297ae16..0000000
--- a/ui/ui-animation-tooling-internal/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" version="4.2.0-beta02">
-
-</issues>
diff --git a/wear/wear-complications-data/api/current.txt b/wear/wear-complications-data/api/current.txt
index d1e2d37..304c950 100644
--- a/wear/wear-complications-data/api/current.txt
+++ b/wear/wear-complications-data/api/current.txt
@@ -252,15 +252,6 @@
     field public static final androidx.wear.complications.data.ComplicationType TYPE;
   }
 
-  public final class IdAndComplicationData {
-    ctor public IdAndComplicationData(int complicationId, androidx.wear.complications.data.ComplicationData complicationData);
-    ctor public IdAndComplicationData(int complicationId, android.support.wearable.complications.ComplicationData complicationData);
-    method public androidx.wear.complications.data.ComplicationData getComplicationData();
-    method public int getComplicationId();
-    property public final androidx.wear.complications.data.ComplicationData complicationData;
-    property public final int complicationId;
-  }
-
   public final class LongTextComplicationData extends androidx.wear.complications.data.ComplicationData {
     method public androidx.wear.complications.data.ComplicationText? getContentDescription();
     method public androidx.wear.complications.data.MonochromaticImage? getMonochromaticImage();
diff --git a/wear/wear-complications-data/api/public_plus_experimental_current.txt b/wear/wear-complications-data/api/public_plus_experimental_current.txt
index 9d6e270..937238d 100644
--- a/wear/wear-complications-data/api/public_plus_experimental_current.txt
+++ b/wear/wear-complications-data/api/public_plus_experimental_current.txt
@@ -254,15 +254,6 @@
     field public static final androidx.wear.complications.data.ComplicationType TYPE;
   }
 
-  public final class IdAndComplicationData {
-    ctor public IdAndComplicationData(int complicationId, androidx.wear.complications.data.ComplicationData complicationData);
-    ctor public IdAndComplicationData(int complicationId, android.support.wearable.complications.ComplicationData complicationData);
-    method public androidx.wear.complications.data.ComplicationData getComplicationData();
-    method public int getComplicationId();
-    property public final androidx.wear.complications.data.ComplicationData complicationData;
-    property public final int complicationId;
-  }
-
   public final class LongTextComplicationData extends androidx.wear.complications.data.ComplicationData {
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public android.support.wearable.complications.ComplicationData asWireComplicationData();
     method public androidx.wear.complications.data.ComplicationText? getContentDescription();
diff --git a/wear/wear-complications-data/api/restricted_current.txt b/wear/wear-complications-data/api/restricted_current.txt
index 580e18d..c9d49f8 100644
--- a/wear/wear-complications-data/api/restricted_current.txt
+++ b/wear/wear-complications-data/api/restricted_current.txt
@@ -312,15 +312,6 @@
     field public static final androidx.wear.complications.data.ComplicationType TYPE;
   }
 
-  public final class IdAndComplicationData {
-    ctor public IdAndComplicationData(int complicationId, androidx.wear.complications.data.ComplicationData complicationData);
-    ctor public IdAndComplicationData(int complicationId, android.support.wearable.complications.ComplicationData complicationData);
-    method public androidx.wear.complications.data.ComplicationData getComplicationData();
-    method public int getComplicationId();
-    property public final androidx.wear.complications.data.ComplicationData complicationData;
-    property public final int complicationId;
-  }
-
   public final class LongTextComplicationData extends androidx.wear.complications.data.ComplicationData {
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY) public android.support.wearable.complications.ComplicationData asWireComplicationData();
     method public androidx.wear.complications.data.ComplicationText? getContentDescription();
diff --git a/wear/wear-complications-data/src/main/java/androidx/wear/complications/data/Data.kt b/wear/wear-complications-data/src/main/java/androidx/wear/complications/data/Data.kt
index 0d3548c..b701dbe 100644
--- a/wear/wear-complications-data/src/main/java/androidx/wear/complications/data/Data.kt
+++ b/wear/wear-complications-data/src/main/java/androidx/wear/complications/data/Data.kt
@@ -57,21 +57,6 @@
         } ?: WireComplicationDataBuilder(type.asWireComplicationType())
 }
 
-/** A pair of id and [ComplicationData]. */
-public class IdAndComplicationData(
-    public val complicationId: Int,
-    public val complicationData: ComplicationData
-) {
-    /** Convenience constructor which accepts a [WireComplicationData]. */
-    public constructor(
-        complicationId: Int,
-        complicationData: WireComplicationData
-    ) : this(
-        complicationId,
-        complicationData.asApiComplicationData()
-    )
-}
-
 /**
  * Type that can be sent by any provider, regardless of the configured type, when the provider
  * has no data to be displayed. Watch faces may choose whether to render this in some way or
diff --git a/wear/wear-complications-data/src/main/java/androidx/wear/complications/data/Text.kt b/wear/wear-complications-data/src/main/java/androidx/wear/complications/data/Text.kt
index 2d5b55c..c05db28 100644
--- a/wear/wear-complications-data/src/main/java/androidx/wear/complications/data/Text.kt
+++ b/wear/wear-complications-data/src/main/java/androidx/wear/complications/data/Text.kt
@@ -203,12 +203,12 @@
         public constructor(
             style: TimeDifferenceStyle,
             countUpTimeReference: CountUpTimeReference
-        ) : this(style, countUpTimeReference.dateTimeMillis, null)
+        ) : this(style, null, countUpTimeReference.dateTimeMillis)
 
         public constructor(
             style: TimeDifferenceStyle,
             countDownTimeReference: CountDownTimeReference
-        ) : this(style, null, countDownTimeReference.dateTimeMillis)
+        ) : this(style, countDownTimeReference.dateTimeMillis, null)
 
         /**
          * Sets the text within which the time difference will be displayed.
diff --git a/wear/wear-complications-data/src/test/java/androidx/wear/complications/data/TextTest.kt b/wear/wear-complications-data/src/test/java/androidx/wear/complications/data/TextTest.kt
index 127676d..aa1c765 100644
--- a/wear/wear-complications-data/src/test/java/androidx/wear/complications/data/TextTest.kt
+++ b/wear/wear-complications-data/src/test/java/androidx/wear/complications/data/TextTest.kt
@@ -49,7 +49,7 @@
         val referenceMillis = Instant.parse("2020-12-30T10:15:30.001Z").toEpochMilli()
         val text = TimeDifferenceComplicationText.Builder(
             TimeDifferenceStyle.STOPWATCH,
-            CountDownTimeReference(referenceMillis)
+            CountUpTimeReference(referenceMillis)
         )
             .setText("^1 after lunch")
             .setDisplayAsNow(false)
@@ -78,7 +78,7 @@
         val referenceMillis = Instant.parse("2020-12-30T10:15:30.001Z").toEpochMilli()
         val text = TimeDifferenceComplicationText.Builder(
             TimeDifferenceStyle.STOPWATCH,
-            CountUpTimeReference(referenceMillis)
+            CountDownTimeReference(referenceMillis)
         )
             .setText("^1 before lunch")
             .setDisplayAsNow(false)
diff --git a/wear/wear-tiles-data/build.gradle b/wear/wear-tiles-data/build.gradle
deleted file mode 100644
index faf2576..0000000
--- a/wear/wear-tiles-data/build.gradle
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import static androidx.build.dependencies.DependenciesKt.*
-import androidx.build.LibraryGroups
-import androidx.build.LibraryType
-import androidx.build.LibraryVersions
-import androidx.build.RunApiTasks
-
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.library")
-}
-
-dependencies {
-    api("androidx.annotation:annotation:1.1.0")
-    api(GUAVA_LISTENABLE_FUTURE)
-    implementation(PROTOBUF_LITE)
-    implementation("androidx.annotation:annotation:1.2.0-alpha01")
-
-    testImplementation(ANDROIDX_TEST_EXT_JUNIT)
-    testImplementation(ANDROIDX_TEST_CORE)
-    testImplementation(ANDROIDX_TEST_RUNNER)
-    testImplementation(ANDROIDX_TEST_RULES)
-    testImplementation(ROBOLECTRIC)
-    testImplementation(MOCKITO_CORE)
-}
-
-android {
-    defaultConfig {
-        minSdkVersion 25
-    }
-
-    buildFeatures {
-        aidl = true
-    }
-
-    // Use Robolectric 4.+
-    testOptions.unitTests.includeAndroidResources = true
-}
-
-androidx {
-    name = "Android Wear Tiles Data"
-    type = LibraryType.PUBLISHED_LIBRARY
-    mavenGroup = LibraryGroups.WEAR
-    mavenVersion = LibraryVersions.WEAR_TILES_DATA
-    inceptionYear = "2020"
-    description = "Android Wear Tiles Internal Data Classes"
-    runApiTasks = new RunApiTasks.No("API tracking disabled while the package is empty")
-}
diff --git a/wear/wear-tiles-data/lint-baseline.xml b/wear/wear-tiles-data/lint-baseline.xml
deleted file mode 100644
index 8f1aa4b..0000000
--- a/wear/wear-tiles-data/lint-baseline.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.2.0-beta02" client="gradle" variant="debug" version="4.2.0-beta02">
-
-</issues>
diff --git a/wear/wear-tiles-data/src/main/AndroidManifest.xml b/wear/wear-tiles-data/src/main/AndroidManifest.xml
deleted file mode 100644
index e26eded..0000000
--- a/wear/wear-tiles-data/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="androidx.wear.tiles">
-</manifest>
diff --git a/wear/wear-watchface-client/api/current.txt b/wear/wear-watchface-client/api/current.txt
index 2136444..0c88581 100644
--- a/wear/wear-watchface-client/api/current.txt
+++ b/wear/wear-watchface-client/api/current.txt
@@ -2,9 +2,10 @@
 package androidx.wear.watchface.client {
 
   public final class ComplicationState {
-    ctor public ComplicationState(android.graphics.Rect bounds, int boundsType, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy, androidx.wear.complications.data.ComplicationType defaultProviderType, boolean isEnabled, boolean isInitiallyEnabled, androidx.wear.complications.data.ComplicationType currentType, boolean fixedComplicationProvider);
+    ctor public ComplicationState(android.graphics.Rect bounds, int boundsType, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy, androidx.wear.complications.data.ComplicationType defaultProviderType, boolean isEnabled, boolean isInitiallyEnabled, androidx.wear.complications.data.ComplicationType currentType, boolean fixedComplicationProvider, android.os.Bundle complicationConfigExtras);
     method public android.graphics.Rect getBounds();
     method public int getBoundsType();
+    method public android.os.Bundle getComplicationConfigExtras();
     method public androidx.wear.complications.data.ComplicationType getCurrentType();
     method public androidx.wear.complications.DefaultComplicationProviderPolicy getDefaultProviderPolicy();
     method public androidx.wear.complications.data.ComplicationType getDefaultProviderType();
@@ -14,6 +15,7 @@
     method public boolean isInitiallyEnabled();
     property public final android.graphics.Rect bounds;
     property public final int boundsType;
+    property public final android.os.Bundle complicationConfigExtras;
     property public final androidx.wear.complications.data.ComplicationType currentType;
     property public final androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy;
     property public final androidx.wear.complications.data.ComplicationType defaultProviderType;
diff --git a/wear/wear-watchface-client/api/public_plus_experimental_current.txt b/wear/wear-watchface-client/api/public_plus_experimental_current.txt
index 220f446..f4ad91e 100644
--- a/wear/wear-watchface-client/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface-client/api/public_plus_experimental_current.txt
@@ -2,9 +2,10 @@
 package androidx.wear.watchface.client {
 
   public final class ComplicationState {
-    ctor public ComplicationState(android.graphics.Rect bounds, @androidx.wear.watchface.data.ComplicationBoundsType int boundsType, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy, androidx.wear.complications.data.ComplicationType defaultProviderType, boolean isEnabled, boolean isInitiallyEnabled, androidx.wear.complications.data.ComplicationType currentType, boolean fixedComplicationProvider);
+    ctor public ComplicationState(android.graphics.Rect bounds, @androidx.wear.watchface.data.ComplicationBoundsType int boundsType, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy, androidx.wear.complications.data.ComplicationType defaultProviderType, boolean isEnabled, boolean isInitiallyEnabled, androidx.wear.complications.data.ComplicationType currentType, boolean fixedComplicationProvider, android.os.Bundle complicationConfigExtras);
     method public android.graphics.Rect getBounds();
     method public int getBoundsType();
+    method public android.os.Bundle getComplicationConfigExtras();
     method public androidx.wear.complications.data.ComplicationType getCurrentType();
     method public androidx.wear.complications.DefaultComplicationProviderPolicy getDefaultProviderPolicy();
     method public androidx.wear.complications.data.ComplicationType getDefaultProviderType();
@@ -14,6 +15,7 @@
     method public boolean isInitiallyEnabled();
     property public final android.graphics.Rect bounds;
     property public final int boundsType;
+    property public final android.os.Bundle complicationConfigExtras;
     property public final androidx.wear.complications.data.ComplicationType currentType;
     property public final androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy;
     property public final androidx.wear.complications.data.ComplicationType defaultProviderType;
diff --git a/wear/wear-watchface-client/api/restricted_current.txt b/wear/wear-watchface-client/api/restricted_current.txt
index 88ceba9..64c52c8 100644
--- a/wear/wear-watchface-client/api/restricted_current.txt
+++ b/wear/wear-watchface-client/api/restricted_current.txt
@@ -2,10 +2,11 @@
 package androidx.wear.watchface.client {
 
   public final class ComplicationState {
-    ctor public ComplicationState(android.graphics.Rect bounds, @androidx.wear.watchface.data.ComplicationBoundsType int boundsType, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy, androidx.wear.complications.data.ComplicationType defaultProviderType, boolean isEnabled, boolean isInitiallyEnabled, androidx.wear.complications.data.ComplicationType currentType, boolean fixedComplicationProvider);
+    ctor public ComplicationState(android.graphics.Rect bounds, @androidx.wear.watchface.data.ComplicationBoundsType int boundsType, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy, androidx.wear.complications.data.ComplicationType defaultProviderType, boolean isEnabled, boolean isInitiallyEnabled, androidx.wear.complications.data.ComplicationType currentType, boolean fixedComplicationProvider, android.os.Bundle complicationConfigExtras);
     ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public ComplicationState(androidx.wear.watchface.data.ComplicationStateWireFormat complicationStateWireFormat);
     method public android.graphics.Rect getBounds();
     method public int getBoundsType();
+    method public android.os.Bundle getComplicationConfigExtras();
     method public androidx.wear.complications.data.ComplicationType getCurrentType();
     method public androidx.wear.complications.DefaultComplicationProviderPolicy getDefaultProviderPolicy();
     method public androidx.wear.complications.data.ComplicationType getDefaultProviderType();
@@ -15,6 +16,7 @@
     method public boolean isInitiallyEnabled();
     property public final android.graphics.Rect bounds;
     property public final int boundsType;
+    property public final android.os.Bundle complicationConfigExtras;
     property public final androidx.wear.complications.data.ComplicationType currentType;
     property public final androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy;
     property public final androidx.wear.complications.data.ComplicationType defaultProviderType;
diff --git a/wear/wear-watchface-client/guava/src/androidTest/java/androidx/wear/watchface/ListenableWatchFaceControlClientTest.kt b/wear/wear-watchface-client/guava/src/androidTest/java/androidx/wear/watchface/ListenableWatchFaceControlClientTest.kt
index c3dec98..12aa604 100644
--- a/wear/wear-watchface-client/guava/src/androidTest/java/androidx/wear/watchface/ListenableWatchFaceControlClientTest.kt
+++ b/wear/wear-watchface-client/guava/src/androidTest/java/androidx/wear/watchface/ListenableWatchFaceControlClientTest.kt
@@ -20,9 +20,11 @@
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
+import android.graphics.Rect
 import android.os.Handler
 import android.os.IBinder
 import android.os.Looper
+import android.view.Surface
 import android.view.SurfaceHolder
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -34,8 +36,12 @@
 import androidx.wear.watchface.samples.createExampleCanvasAnalogWatchFaceBuilder
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertNull
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
 import java.util.concurrent.TimeUnit
 
 private const val TIMEOUT_MS = 500L
@@ -44,6 +50,19 @@
 @MediumTest
 public class ListenableWatchFaceControlClientTest {
 
+    @Mock
+    private lateinit var surfaceHolder: SurfaceHolder
+    @Mock
+    private lateinit var surface: Surface
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        Mockito.`when`(surfaceHolder.surfaceFrame)
+            .thenReturn(Rect(0, 0, 400, 400))
+        Mockito.`when`(surfaceHolder.surface).thenReturn(surface)
+    }
+
     @Test
     public fun headlessSchemaSettingIds() {
         val context = ApplicationProvider.getApplicationContext<Context>()
@@ -125,7 +144,12 @@
                 attachBaseContext(context)
             }
         }
-        service.onCreateEngine()
+        service.onCreateEngine().onSurfaceChanged(
+            surfaceHolder,
+            0,
+            surfaceHolder.surfaceFrame.width(),
+            surfaceHolder.surfaceFrame.height()
+        )
 
         val interactiveInstance = interactiveInstanceFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
         assertThat(interactiveInstance.userStyleSchema.userStyleSettings.map { it.id })
@@ -215,7 +239,12 @@
                 attachBaseContext(context)
             }
         }
-        service.onCreateEngine()
+        service.onCreateEngine().onSurfaceChanged(
+            surfaceHolder,
+            0,
+            surfaceHolder.surfaceFrame.width(),
+            surfaceHolder.surfaceFrame.height()
+        )
 
         val interactiveInstance = interactiveInstanceFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
         val headlessInstance1 = client.createHeadlessWatchFaceClient(
diff --git a/wear/wear-watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt b/wear/wear-watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
index cd927f1..6548a2e 100644
--- a/wear/wear-watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
+++ b/wear/wear-watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
@@ -24,6 +24,7 @@
 import android.os.Handler
 import android.os.Looper
 import android.service.wallpaper.WallpaperService
+import android.view.Surface
 import android.view.SurfaceHolder
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -88,6 +89,8 @@
 
     @Mock
     private lateinit var surfaceHolder: SurfaceHolder
+    @Mock
+    private lateinit var surface: Surface
     private lateinit var engine: WallpaperService.Engine
     private val handler = Handler(Looper.getMainLooper())
     private val engineLatch = CountDownLatch(1)
@@ -100,6 +103,7 @@
 
         Mockito.`when`(surfaceHolder.surfaceFrame)
             .thenReturn(Rect(0, 0, 400, 400))
+        Mockito.`when`(surfaceHolder.surface).thenReturn(surface)
     }
 
     @After
@@ -141,6 +145,12 @@
     private fun createEngine() {
         handler.post {
             engine = wallpaperService.onCreateEngine()
+            engine.onSurfaceChanged(
+                surfaceHolder,
+                0,
+                surfaceHolder.surfaceFrame.width(),
+                surfaceHolder.surfaceFrame.height()
+            )
             engineLatch.countDown()
         }
         engineLatch.await(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
@@ -537,7 +547,15 @@
         assertFalse(deferredExistingInstance.isCompleted)
 
         // We don't want to leave a pending request or it'll mess up subsequent tests.
-        handler.post { engine = wallpaperService.onCreateEngine() }
+        handler.post {
+            engine = wallpaperService.onCreateEngine()
+            engine.onSurfaceChanged(
+                surfaceHolder,
+                0,
+                surfaceHolder.surfaceFrame.width(),
+                surfaceHolder.surfaceFrame.height()
+            )
+        }
         runBlocking {
             withTimeout(CONNECT_TIMEOUT_MILLIS) {
                 deferredExistingInstance.await()
diff --git a/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/ComplicationState.kt b/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/ComplicationState.kt
index 8048908..4023d7c 100644
--- a/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/ComplicationState.kt
+++ b/wear/wear-watchface-client/src/main/java/androidx/wear/watchface/client/ComplicationState.kt
@@ -17,6 +17,7 @@
 package androidx.wear.watchface.client
 
 import android.graphics.Rect
+import android.os.Bundle
 import androidx.annotation.RestrictTo
 import androidx.wear.complications.DefaultComplicationProviderPolicy
 import androidx.wear.complications.data.ComplicationData
@@ -60,7 +61,10 @@
 
     /** Whether or not the complication provider is fixed (i.e the user can't configure it). */
     @get:JvmName("isFixedComplicationProvider")
-    public val fixedComplicationProvider: Boolean
+    public val fixedComplicationProvider: Boolean,
+
+    /** Extras to be merged into the Intent sent when invoking the provider chooser activity. */
+    public val complicationConfigExtras: Bundle
 ) {
     /** @hide */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -78,6 +82,7 @@
         complicationStateWireFormat.isEnabled,
         complicationStateWireFormat.isInitiallyEnabled,
         ComplicationType.fromWireType(complicationStateWireFormat.currentType),
-        complicationStateWireFormat.isFixedComplicationProvider
+        complicationStateWireFormat.isFixedComplicationProvider,
+        complicationStateWireFormat.complicationConfigExtras
     )
 }
\ No newline at end of file
diff --git a/wear/wear-watchface-data/api/restricted_current.txt b/wear/wear-watchface-data/api/restricted_current.txt
index 8796d6c..3415e93 100644
--- a/wear/wear-watchface-data/api/restricted_current.txt
+++ b/wear/wear-watchface-data/api/restricted_current.txt
@@ -189,10 +189,11 @@
   }
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.versionedparcelable.VersionedParcelize public final class ComplicationStateWireFormat implements android.os.Parcelable androidx.versionedparcelable.VersionedParcelable {
-    ctor public ComplicationStateWireFormat(android.graphics.Rect, @androidx.wear.watchface.data.ComplicationBoundsType int, @android.support.wearable.complications.ComplicationData.ComplicationType int[], java.util.List<android.content.ComponentName!>?, int, @android.support.wearable.complications.ComplicationData.ComplicationType int, boolean, boolean, @android.support.wearable.complications.ComplicationData.ComplicationType int, boolean);
+    ctor public ComplicationStateWireFormat(android.graphics.Rect, @androidx.wear.watchface.data.ComplicationBoundsType int, @android.support.wearable.complications.ComplicationData.ComplicationType int[], java.util.List<android.content.ComponentName!>?, int, @android.support.wearable.complications.ComplicationData.ComplicationType int, boolean, boolean, @android.support.wearable.complications.ComplicationData.ComplicationType int, boolean, android.os.Bundle);
     method public int describeContents();
     method public android.graphics.Rect getBounds();
     method @androidx.wear.watchface.data.ComplicationBoundsType public int getBoundsType();
+    method public android.os.Bundle getComplicationConfigExtras();
     method @android.support.wearable.complications.ComplicationData.ComplicationType public int getCurrentType();
     method @android.support.wearable.complications.ComplicationData.ComplicationType public int getDefaultProviderType();
     method public java.util.List<android.content.ComponentName!>? getDefaultProvidersToTry();
diff --git a/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/data/ComplicationStateWireFormat.java b/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/data/ComplicationStateWireFormat.java
index 530ea84..d089609 100644
--- a/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/data/ComplicationStateWireFormat.java
+++ b/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/data/ComplicationStateWireFormat.java
@@ -19,6 +19,7 @@
 import android.annotation.SuppressLint;
 import android.content.ComponentName;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.support.wearable.complications.ComplicationData;
@@ -79,6 +80,10 @@
     @ParcelField(10)
     boolean mFixedComplicationProvider;
 
+    @ParcelField(11)
+    @NonNull
+    Bundle mComplicationConfigExtras;
+
     /** Used by VersionedParcelable. */
     ComplicationStateWireFormat() {
     }
@@ -93,7 +98,8 @@
             boolean isEnabled,
             boolean isInitiallyEnabled,
             @ComplicationData.ComplicationType int currentType,
-            boolean fixedComplicationProvider) {
+            boolean fixedComplicationProvider,
+            @NonNull Bundle complicationConfigExtras) {
         mBounds = bounds;
         mBoundsType = boundsType;
         mSupportedTypes = supportedTypes;
@@ -104,6 +110,7 @@
         mIsInitiallyEnabled = isInitiallyEnabled;
         mCurrentType = currentType;
         mFixedComplicationProvider = fixedComplicationProvider;
+        mComplicationConfigExtras = complicationConfigExtras;
     }
 
     @NonNull
@@ -162,6 +169,11 @@
         return mCurrentType;
     }
 
+    @NonNull
+    public Bundle getComplicationConfigExtras() {
+        return mComplicationConfigExtras;
+    }
+
     /** Serializes this ComplicationDetails to the specified {@link Parcel}. */
     @Override
     public void writeToParcel(@NonNull Parcel parcel, int flags) {
diff --git a/wear/wear-watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditingSessionTest.kt b/wear/wear-watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditingSessionTest.kt
index cf2afa0..2aeab67 100644
--- a/wear/wear-watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditingSessionTest.kt
+++ b/wear/wear-watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditingSessionTest.kt
@@ -89,6 +89,9 @@
 
 private const val TIMEOUT_MILLIS = 500L
 
+private const val PROVIDER_CHOOSER_EXTRA_KEY = "PROVIDER_CHOOSER_EXTRA_KEY"
+private const val PROVIDER_CHOOSER_EXTRA_VALUE = "PROVIDER_CHOOSER_EXTRA_VALUE"
+
 /** Trivial "editor" which exposes the EditorSession for testing. */
 public open class OnWatchFaceEditingTestActivity : ComponentActivity() {
     public lateinit var editorSession: EditorSession
@@ -312,6 +315,11 @@
             DefaultComplicationProviderPolicy(SystemProviders.DAY_OF_WEEK),
             ComplicationBounds(RectF(0.6f, 0.4f, 0.8f, 0.6f))
         ).setDefaultProviderType(ComplicationType.SHORT_TEXT)
+            .setConfigExtras(
+                Bundle().apply {
+                    putString(PROVIDER_CHOOSER_EXTRA_KEY, PROVIDER_CHOOSER_EXTRA_VALUE)
+                }
+            )
             .build()
 
     private val mockBackgroundCanvasComplication = Mockito.mock(CanvasComplication::class.java)
@@ -738,11 +746,7 @@
              * Invoke [TestComplicationHelperActivity] which will change the provider (and hence
              * the preview data) for [LEFT_COMPLICATION_ID].
              */
-            assertTrue(
-                editorSession.launchComplicationProviderChooser(
-                    LEFT_COMPLICATION_ID
-                )
-            )
+            assertTrue(editorSession.launchComplicationProviderChooser(LEFT_COMPLICATION_ID))
 
             // This should update the preview data to point to the updated provider3 data.
             val previewComplication =
@@ -765,6 +769,31 @@
     }
 
     @Test
+    public fun launchComplicationProviderChooser_ComplicationConfigExtras() {
+        ComplicationProviderChooserContract.useTestComplicationHelperActivity = true
+
+        val scenario = createOnWatchFaceEditingTestActivity(
+            emptyList(),
+            listOf(leftComplication, rightComplication)
+        )
+
+        lateinit var editorSession: EditorSession
+        scenario.onActivity { activity ->
+            editorSession = activity.editorSession
+        }
+
+        runBlocking {
+            assertTrue(editorSession.launchComplicationProviderChooser(RIGHT_COMPLICATION_ID))
+
+            assertThat(
+                TestComplicationHelperActivity.lastIntent?.extras?.getString(
+                    PROVIDER_CHOOSER_EXTRA_KEY
+                )
+            ).isEqualTo(PROVIDER_CHOOSER_EXTRA_VALUE)
+        }
+    }
+
+    @Test
     public fun getComplicationIdAt() {
         val scenario = createOnWatchFaceEditingTestActivity(
             emptyList(),
diff --git a/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt b/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
index bd3a8c1..d61d42a 100644
--- a/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
+++ b/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
@@ -21,6 +21,7 @@
 import android.content.Context
 import android.content.Intent
 import android.graphics.Bitmap
+import android.os.Bundle
 import android.os.Handler
 import android.os.Looper
 import android.support.wearable.complications.ComplicationProviderInfo
@@ -488,9 +489,9 @@
                 it.value.defaultProviderType,
                 it.value.enabled,
                 it.value.initiallyEnabled,
-                it.value.renderer.getIdAndData()?.complicationData?.type
-                    ?: ComplicationType.NO_DATA,
-                it.value.fixedComplicationProvider
+                it.value.renderer.getData()?.type ?: ComplicationType.NO_DATA,
+                it.value.fixedComplicationProvider,
+                it.value.configExtras
             )
         }
 
@@ -620,6 +621,10 @@
             input.editorSession.complicationState[input.complicationId]!!.supportedTypes,
             input.instanceId
         )
+        val complicationState = input.editorSession.complicationState[input.complicationId]!!
+        intent.replaceExtras(
+            Bundle(complicationState.complicationConfigExtras).apply { putAll(intent.extras!!) }
+        )
         if (useTestComplicationHelperActivity) {
             intent.component = ComponentName(
                 "androidx.wear.watchface.editor.test",
diff --git a/wear/wear-watchface/api/current.txt b/wear/wear-watchface/api/current.txt
index 289dc0a..45f5082 100644
--- a/wear/wear-watchface/api/current.txt
+++ b/wear/wear-watchface/api/current.txt
@@ -2,29 +2,25 @@
 package androidx.wear.watchface {
 
   public interface CanvasComplication {
-    method public void clearIdAndData();
-    method public androidx.wear.complications.data.IdAndComplicationData? getIdAndData();
+    method public androidx.wear.complications.data.ComplicationData? getData();
     method public boolean isHighlighted();
     method @UiThread public void onAttach(androidx.wear.watchface.Complication complication);
-    method @UiThread public void onDetach();
-    method @UiThread public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
-    method public void setIdAndData(androidx.wear.complications.data.IdAndComplicationData? idAndComplicationData, boolean loadDrawablesAsynchronous);
+    method @UiThread public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters, int complicationId);
+    method public void setData(androidx.wear.complications.data.ComplicationData? complicationData, boolean loadDrawablesAsynchronous);
     method public void setIsHighlighted(boolean p);
     property public abstract boolean isHighlighted;
   }
 
   public class CanvasComplicationDrawable implements androidx.wear.watchface.CanvasComplication {
     ctor public CanvasComplicationDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable, androidx.wear.watchface.WatchState watchState);
-    method public void clearIdAndData();
     method public void drawOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, @ColorInt int color);
+    method public androidx.wear.complications.data.ComplicationData? getData();
     method public final androidx.wear.watchface.complications.rendering.ComplicationDrawable getDrawable();
-    method public androidx.wear.complications.data.IdAndComplicationData? getIdAndData();
     method @UiThread public boolean isHighlighted();
     method public void onAttach(androidx.wear.watchface.Complication complication);
-    method public void onDetach();
-    method public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
+    method public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters, int complicationId);
+    method public void setData(androidx.wear.complications.data.ComplicationData? complicationData, boolean loadDrawablesAsynchronous);
     method public final void setDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable value);
-    method public void setIdAndData(androidx.wear.complications.data.IdAndComplicationData? idAndComplicationData, boolean loadDrawablesAsynchronous);
     method @UiThread public void setIsHighlighted(boolean value);
     property public final androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable;
     property @UiThread public boolean isHighlighted;
@@ -33,14 +29,14 @@
   public final class Complication {
     method public android.graphics.Rect computeBounds(android.graphics.Rect screen);
     method public static androidx.wear.watchface.Complication.Builder createBackgroundComplicationBuilder(int id, androidx.wear.watchface.CanvasComplication renderer, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy);
-    method public static androidx.wear.watchface.Complication.Builder createRoundRectComplicationBuilder(int id, androidx.wear.watchface.CanvasComplication renderer, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy, androidx.wear.complications.ComplicationBounds complicationBounds);
+    method public static androidx.wear.watchface.Complication.Builder createRoundRectComplicationBuilder(int id, androidx.wear.watchface.CanvasComplication renderer, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy, androidx.wear.complications.ComplicationBounds bounds);
     method public int getBoundsType();
     method @UiThread public androidx.wear.complications.ComplicationBounds getComplicationBounds();
-    method public android.os.Bundle? getComplicationConfigExtras();
     method public androidx.wear.watchface.ObservableWatchData<androidx.wear.complications.data.ComplicationData> getComplicationData();
+    method public android.os.Bundle getConfigExtras();
     method @UiThread public androidx.wear.complications.DefaultComplicationProviderPolicy getDefaultProviderPolicy();
     method @UiThread public androidx.wear.complications.data.ComplicationType getDefaultProviderType();
-    method @UiThread public androidx.wear.watchface.CanvasComplication getRenderer();
+    method public androidx.wear.watchface.CanvasComplication getRenderer();
     method @UiThread public java.util.List<androidx.wear.complications.data.ComplicationType> getSupportedTypes();
     method public void invalidate();
     method public boolean isActiveAt(long dateTimeMillis);
@@ -48,25 +44,23 @@
     method public boolean isFixedComplicationProvider();
     method public boolean isInitiallyEnabled();
     method @UiThread public void render(android.graphics.Canvas canvas, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
-    method @UiThread public void setComplicationBounds(androidx.wear.complications.ComplicationBounds value);
-    method @UiThread public void setRenderer(androidx.wear.watchface.CanvasComplication value);
     property public final int boundsType;
     property @UiThread public final androidx.wear.complications.ComplicationBounds complicationBounds;
-    property public final android.os.Bundle? complicationConfigExtras;
     property public final androidx.wear.watchface.ObservableWatchData<androidx.wear.complications.data.ComplicationData> complicationData;
+    property public final android.os.Bundle configExtras;
     property @UiThread public final androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy;
     property @UiThread public final androidx.wear.complications.data.ComplicationType defaultProviderType;
     property @UiThread public final boolean enabled;
     property public final boolean fixedComplicationProvider;
     property public final boolean initiallyEnabled;
-    property @UiThread public final androidx.wear.watchface.CanvasComplication renderer;
+    property public final androidx.wear.watchface.CanvasComplication renderer;
     property @UiThread public final java.util.List<androidx.wear.complications.data.ComplicationType> supportedTypes;
     field public static final androidx.wear.watchface.Complication.Companion Companion;
   }
 
   public static final class Complication.Builder {
     method public androidx.wear.watchface.Complication build();
-    method public androidx.wear.watchface.Complication.Builder setComplicationConfigExtras(android.os.Bundle? extras);
+    method public androidx.wear.watchface.Complication.Builder setConfigExtras(android.os.Bundle extras);
     method public androidx.wear.watchface.Complication.Builder setDefaultProviderType(androidx.wear.complications.data.ComplicationType defaultProviderType);
     method public androidx.wear.watchface.Complication.Builder setEnabled(boolean enabled);
     method public androidx.wear.watchface.Complication.Builder setFixedComplicationProvider(boolean fixedComplicationProvider);
@@ -74,17 +68,17 @@
 
   public static final class Complication.Companion {
     method public androidx.wear.watchface.Complication.Builder createBackgroundComplicationBuilder(int id, androidx.wear.watchface.CanvasComplication renderer, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy);
-    method public androidx.wear.watchface.Complication.Builder createRoundRectComplicationBuilder(int id, androidx.wear.watchface.CanvasComplication renderer, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy, androidx.wear.complications.ComplicationBounds complicationBounds);
+    method public androidx.wear.watchface.Complication.Builder createRoundRectComplicationBuilder(int id, androidx.wear.watchface.CanvasComplication renderer, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy, androidx.wear.complications.ComplicationBounds bounds);
   }
 
   public final class ComplicationOutlineRenderer {
     ctor public ComplicationOutlineRenderer();
-    method public static void drawComplicationSelectOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, @ColorInt int color);
+    method public static void drawComplicationOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, @ColorInt int color);
     field public static final androidx.wear.watchface.ComplicationOutlineRenderer.Companion Companion;
   }
 
   public static final class ComplicationOutlineRenderer.Companion {
-    method public void drawComplicationSelectOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, @ColorInt int color);
+    method public void drawComplicationOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, @ColorInt int color);
   }
 
   public final class ComplicationsManager {
@@ -93,14 +87,14 @@
     method @UiThread public void bringAttentionToComplication(int complicationId);
     method public operator androidx.wear.watchface.Complication? get(int id);
     method public androidx.wear.watchface.Complication? getBackgroundComplication();
-    method public androidx.wear.watchface.Complication? getComplicationAt(int x, int y);
+    method public androidx.wear.watchface.Complication? getComplicationAt(@Px int x, @Px int y);
     method public java.util.Map<java.lang.Integer,androidx.wear.watchface.Complication> getComplications();
     method @UiThread public void removeTapListener(androidx.wear.watchface.ComplicationsManager.TapCallback tapCallback);
     property public final java.util.Map<java.lang.Integer,androidx.wear.watchface.Complication> complications;
   }
 
   public static interface ComplicationsManager.TapCallback {
-    method public default void onComplicationSingleTapped(int complicationId);
+    method public default void onComplicationTapped(int complicationId);
   }
 
   public final class ComplicationsManagerKt {
@@ -114,11 +108,13 @@
   }
 
   public final class GlesTextureComplication {
-    ctor public GlesTextureComplication(androidx.wear.watchface.CanvasComplication canvasComplication, @Px int textureWidth, @Px int textureHeight, int textureType);
+    ctor public GlesTextureComplication(androidx.wear.watchface.CanvasComplication canvasComplication, @Px int textureWidth, @Px int textureHeight, int textureType, int id);
     method public void bind();
     method public androidx.wear.watchface.CanvasComplication getCanvasComplication();
+    method public int getId();
     method public void renderToTexture(android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
     property public final androidx.wear.watchface.CanvasComplication canvasComplication;
+    property public final int id;
   }
 
   public enum LayerMode {
@@ -153,11 +149,11 @@
     ctor public RenderParameters(androidx.wear.watchface.DrawMode drawMode, java.util.Map<androidx.wear.watchface.style.Layer,? extends androidx.wear.watchface.LayerMode> layerParameters, Integer? selectedComplicationId);
     method public androidx.wear.watchface.DrawMode getDrawMode();
     method public java.util.Map<androidx.wear.watchface.style.Layer,androidx.wear.watchface.LayerMode> getLayerParameters();
-    method public int getOutlineTint();
+    method @ColorInt public int getOutlineTint();
     method public Integer? getSelectedComplicationId();
     property public final androidx.wear.watchface.DrawMode drawMode;
     property public final java.util.Map<androidx.wear.watchface.style.Layer,androidx.wear.watchface.LayerMode> layerParameters;
-    property public final int outlineTint;
+    property @ColorInt public final int outlineTint;
     property public final Integer? selectedComplicationId;
     field public static final androidx.wear.watchface.RenderParameters.Companion Companion;
     field public static final androidx.wear.watchface.RenderParameters DEFAULT_INTERACTIVE;
@@ -190,17 +186,27 @@
   }
 
   public abstract static class Renderer.CanvasRenderer extends androidx.wear.watchface.Renderer {
-    ctor public Renderer.CanvasRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, int canvasType, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis);
+    ctor public Renderer.CanvasRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, int canvasType, @IntRange(from=0, to=60000) long interactiveDrawModeUpdateDelayMillis);
     method @UiThread public abstract void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar);
   }
 
   public abstract static class Renderer.GlesRenderer extends androidx.wear.watchface.Renderer {
-    ctor public Renderer.GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis, optional int[] eglConfigAttribList, optional int[] eglSurfaceAttribList);
-    ctor public Renderer.GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis, optional int[] eglConfigAttribList);
-    ctor public Renderer.GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis);
+    ctor public Renderer.GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=60000) long interactiveDrawModeUpdateDelayMillis, optional int[] eglConfigAttribList, optional int[] eglSurfaceAttribList);
+    ctor public Renderer.GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=60000) long interactiveDrawModeUpdateDelayMillis, optional int[] eglConfigAttribList);
+    ctor public Renderer.GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=60000) long interactiveDrawModeUpdateDelayMillis);
+    method public final android.opengl.EGLConfig getEglConfig();
+    method public final android.opengl.EGLContext? getEglContext();
+    method public final android.opengl.EGLDisplay? getEglDisplay();
+    method @UiThread public final void initOpenGlContext();
     method @UiThread public void onGlContextCreated();
-    method @UiThread public void onGlSurfaceCreated(int width, int height);
+    method @UiThread public void onGlSurfaceCreated(@Px int width, @Px int height);
     method @UiThread public abstract void render(android.icu.util.Calendar calendar);
+    method public final void setEglConfig(android.opengl.EGLConfig p);
+    method public final void setEglContext(android.opengl.EGLContext? p);
+    method public final void setEglDisplay(android.opengl.EGLDisplay? p);
+    property public final android.opengl.EGLConfig eglConfig;
+    property public final android.opengl.EGLContext? eglContext;
+    property public final android.opengl.EGLDisplay? eglDisplay;
   }
 
   public final class RendererKt {
diff --git a/wear/wear-watchface/api/public_plus_experimental_current.txt b/wear/wear-watchface/api/public_plus_experimental_current.txt
index 289dc0a..45f5082 100644
--- a/wear/wear-watchface/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface/api/public_plus_experimental_current.txt
@@ -2,29 +2,25 @@
 package androidx.wear.watchface {
 
   public interface CanvasComplication {
-    method public void clearIdAndData();
-    method public androidx.wear.complications.data.IdAndComplicationData? getIdAndData();
+    method public androidx.wear.complications.data.ComplicationData? getData();
     method public boolean isHighlighted();
     method @UiThread public void onAttach(androidx.wear.watchface.Complication complication);
-    method @UiThread public void onDetach();
-    method @UiThread public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
-    method public void setIdAndData(androidx.wear.complications.data.IdAndComplicationData? idAndComplicationData, boolean loadDrawablesAsynchronous);
+    method @UiThread public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters, int complicationId);
+    method public void setData(androidx.wear.complications.data.ComplicationData? complicationData, boolean loadDrawablesAsynchronous);
     method public void setIsHighlighted(boolean p);
     property public abstract boolean isHighlighted;
   }
 
   public class CanvasComplicationDrawable implements androidx.wear.watchface.CanvasComplication {
     ctor public CanvasComplicationDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable, androidx.wear.watchface.WatchState watchState);
-    method public void clearIdAndData();
     method public void drawOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, @ColorInt int color);
+    method public androidx.wear.complications.data.ComplicationData? getData();
     method public final androidx.wear.watchface.complications.rendering.ComplicationDrawable getDrawable();
-    method public androidx.wear.complications.data.IdAndComplicationData? getIdAndData();
     method @UiThread public boolean isHighlighted();
     method public void onAttach(androidx.wear.watchface.Complication complication);
-    method public void onDetach();
-    method public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
+    method public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters, int complicationId);
+    method public void setData(androidx.wear.complications.data.ComplicationData? complicationData, boolean loadDrawablesAsynchronous);
     method public final void setDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable value);
-    method public void setIdAndData(androidx.wear.complications.data.IdAndComplicationData? idAndComplicationData, boolean loadDrawablesAsynchronous);
     method @UiThread public void setIsHighlighted(boolean value);
     property public final androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable;
     property @UiThread public boolean isHighlighted;
@@ -33,14 +29,14 @@
   public final class Complication {
     method public android.graphics.Rect computeBounds(android.graphics.Rect screen);
     method public static androidx.wear.watchface.Complication.Builder createBackgroundComplicationBuilder(int id, androidx.wear.watchface.CanvasComplication renderer, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy);
-    method public static androidx.wear.watchface.Complication.Builder createRoundRectComplicationBuilder(int id, androidx.wear.watchface.CanvasComplication renderer, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy, androidx.wear.complications.ComplicationBounds complicationBounds);
+    method public static androidx.wear.watchface.Complication.Builder createRoundRectComplicationBuilder(int id, androidx.wear.watchface.CanvasComplication renderer, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy, androidx.wear.complications.ComplicationBounds bounds);
     method public int getBoundsType();
     method @UiThread public androidx.wear.complications.ComplicationBounds getComplicationBounds();
-    method public android.os.Bundle? getComplicationConfigExtras();
     method public androidx.wear.watchface.ObservableWatchData<androidx.wear.complications.data.ComplicationData> getComplicationData();
+    method public android.os.Bundle getConfigExtras();
     method @UiThread public androidx.wear.complications.DefaultComplicationProviderPolicy getDefaultProviderPolicy();
     method @UiThread public androidx.wear.complications.data.ComplicationType getDefaultProviderType();
-    method @UiThread public androidx.wear.watchface.CanvasComplication getRenderer();
+    method public androidx.wear.watchface.CanvasComplication getRenderer();
     method @UiThread public java.util.List<androidx.wear.complications.data.ComplicationType> getSupportedTypes();
     method public void invalidate();
     method public boolean isActiveAt(long dateTimeMillis);
@@ -48,25 +44,23 @@
     method public boolean isFixedComplicationProvider();
     method public boolean isInitiallyEnabled();
     method @UiThread public void render(android.graphics.Canvas canvas, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
-    method @UiThread public void setComplicationBounds(androidx.wear.complications.ComplicationBounds value);
-    method @UiThread public void setRenderer(androidx.wear.watchface.CanvasComplication value);
     property public final int boundsType;
     property @UiThread public final androidx.wear.complications.ComplicationBounds complicationBounds;
-    property public final android.os.Bundle? complicationConfigExtras;
     property public final androidx.wear.watchface.ObservableWatchData<androidx.wear.complications.data.ComplicationData> complicationData;
+    property public final android.os.Bundle configExtras;
     property @UiThread public final androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy;
     property @UiThread public final androidx.wear.complications.data.ComplicationType defaultProviderType;
     property @UiThread public final boolean enabled;
     property public final boolean fixedComplicationProvider;
     property public final boolean initiallyEnabled;
-    property @UiThread public final androidx.wear.watchface.CanvasComplication renderer;
+    property public final androidx.wear.watchface.CanvasComplication renderer;
     property @UiThread public final java.util.List<androidx.wear.complications.data.ComplicationType> supportedTypes;
     field public static final androidx.wear.watchface.Complication.Companion Companion;
   }
 
   public static final class Complication.Builder {
     method public androidx.wear.watchface.Complication build();
-    method public androidx.wear.watchface.Complication.Builder setComplicationConfigExtras(android.os.Bundle? extras);
+    method public androidx.wear.watchface.Complication.Builder setConfigExtras(android.os.Bundle extras);
     method public androidx.wear.watchface.Complication.Builder setDefaultProviderType(androidx.wear.complications.data.ComplicationType defaultProviderType);
     method public androidx.wear.watchface.Complication.Builder setEnabled(boolean enabled);
     method public androidx.wear.watchface.Complication.Builder setFixedComplicationProvider(boolean fixedComplicationProvider);
@@ -74,17 +68,17 @@
 
   public static final class Complication.Companion {
     method public androidx.wear.watchface.Complication.Builder createBackgroundComplicationBuilder(int id, androidx.wear.watchface.CanvasComplication renderer, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy);
-    method public androidx.wear.watchface.Complication.Builder createRoundRectComplicationBuilder(int id, androidx.wear.watchface.CanvasComplication renderer, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy, androidx.wear.complications.ComplicationBounds complicationBounds);
+    method public androidx.wear.watchface.Complication.Builder createRoundRectComplicationBuilder(int id, androidx.wear.watchface.CanvasComplication renderer, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy, androidx.wear.complications.ComplicationBounds bounds);
   }
 
   public final class ComplicationOutlineRenderer {
     ctor public ComplicationOutlineRenderer();
-    method public static void drawComplicationSelectOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, @ColorInt int color);
+    method public static void drawComplicationOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, @ColorInt int color);
     field public static final androidx.wear.watchface.ComplicationOutlineRenderer.Companion Companion;
   }
 
   public static final class ComplicationOutlineRenderer.Companion {
-    method public void drawComplicationSelectOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, @ColorInt int color);
+    method public void drawComplicationOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, @ColorInt int color);
   }
 
   public final class ComplicationsManager {
@@ -93,14 +87,14 @@
     method @UiThread public void bringAttentionToComplication(int complicationId);
     method public operator androidx.wear.watchface.Complication? get(int id);
     method public androidx.wear.watchface.Complication? getBackgroundComplication();
-    method public androidx.wear.watchface.Complication? getComplicationAt(int x, int y);
+    method public androidx.wear.watchface.Complication? getComplicationAt(@Px int x, @Px int y);
     method public java.util.Map<java.lang.Integer,androidx.wear.watchface.Complication> getComplications();
     method @UiThread public void removeTapListener(androidx.wear.watchface.ComplicationsManager.TapCallback tapCallback);
     property public final java.util.Map<java.lang.Integer,androidx.wear.watchface.Complication> complications;
   }
 
   public static interface ComplicationsManager.TapCallback {
-    method public default void onComplicationSingleTapped(int complicationId);
+    method public default void onComplicationTapped(int complicationId);
   }
 
   public final class ComplicationsManagerKt {
@@ -114,11 +108,13 @@
   }
 
   public final class GlesTextureComplication {
-    ctor public GlesTextureComplication(androidx.wear.watchface.CanvasComplication canvasComplication, @Px int textureWidth, @Px int textureHeight, int textureType);
+    ctor public GlesTextureComplication(androidx.wear.watchface.CanvasComplication canvasComplication, @Px int textureWidth, @Px int textureHeight, int textureType, int id);
     method public void bind();
     method public androidx.wear.watchface.CanvasComplication getCanvasComplication();
+    method public int getId();
     method public void renderToTexture(android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
     property public final androidx.wear.watchface.CanvasComplication canvasComplication;
+    property public final int id;
   }
 
   public enum LayerMode {
@@ -153,11 +149,11 @@
     ctor public RenderParameters(androidx.wear.watchface.DrawMode drawMode, java.util.Map<androidx.wear.watchface.style.Layer,? extends androidx.wear.watchface.LayerMode> layerParameters, Integer? selectedComplicationId);
     method public androidx.wear.watchface.DrawMode getDrawMode();
     method public java.util.Map<androidx.wear.watchface.style.Layer,androidx.wear.watchface.LayerMode> getLayerParameters();
-    method public int getOutlineTint();
+    method @ColorInt public int getOutlineTint();
     method public Integer? getSelectedComplicationId();
     property public final androidx.wear.watchface.DrawMode drawMode;
     property public final java.util.Map<androidx.wear.watchface.style.Layer,androidx.wear.watchface.LayerMode> layerParameters;
-    property public final int outlineTint;
+    property @ColorInt public final int outlineTint;
     property public final Integer? selectedComplicationId;
     field public static final androidx.wear.watchface.RenderParameters.Companion Companion;
     field public static final androidx.wear.watchface.RenderParameters DEFAULT_INTERACTIVE;
@@ -190,17 +186,27 @@
   }
 
   public abstract static class Renderer.CanvasRenderer extends androidx.wear.watchface.Renderer {
-    ctor public Renderer.CanvasRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, int canvasType, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis);
+    ctor public Renderer.CanvasRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, int canvasType, @IntRange(from=0, to=60000) long interactiveDrawModeUpdateDelayMillis);
     method @UiThread public abstract void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar);
   }
 
   public abstract static class Renderer.GlesRenderer extends androidx.wear.watchface.Renderer {
-    ctor public Renderer.GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis, optional int[] eglConfigAttribList, optional int[] eglSurfaceAttribList);
-    ctor public Renderer.GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis, optional int[] eglConfigAttribList);
-    ctor public Renderer.GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis);
+    ctor public Renderer.GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=60000) long interactiveDrawModeUpdateDelayMillis, optional int[] eglConfigAttribList, optional int[] eglSurfaceAttribList);
+    ctor public Renderer.GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=60000) long interactiveDrawModeUpdateDelayMillis, optional int[] eglConfigAttribList);
+    ctor public Renderer.GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=60000) long interactiveDrawModeUpdateDelayMillis);
+    method public final android.opengl.EGLConfig getEglConfig();
+    method public final android.opengl.EGLContext? getEglContext();
+    method public final android.opengl.EGLDisplay? getEglDisplay();
+    method @UiThread public final void initOpenGlContext();
     method @UiThread public void onGlContextCreated();
-    method @UiThread public void onGlSurfaceCreated(int width, int height);
+    method @UiThread public void onGlSurfaceCreated(@Px int width, @Px int height);
     method @UiThread public abstract void render(android.icu.util.Calendar calendar);
+    method public final void setEglConfig(android.opengl.EGLConfig p);
+    method public final void setEglContext(android.opengl.EGLContext? p);
+    method public final void setEglDisplay(android.opengl.EGLDisplay? p);
+    property public final android.opengl.EGLConfig eglConfig;
+    property public final android.opengl.EGLContext? eglContext;
+    property public final android.opengl.EGLDisplay? eglDisplay;
   }
 
   public final class RendererKt {
diff --git a/wear/wear-watchface/api/restricted_current.txt b/wear/wear-watchface/api/restricted_current.txt
index eb61cbf..5455d63 100644
--- a/wear/wear-watchface/api/restricted_current.txt
+++ b/wear/wear-watchface/api/restricted_current.txt
@@ -2,29 +2,25 @@
 package androidx.wear.watchface {
 
   public interface CanvasComplication {
-    method public void clearIdAndData();
-    method public androidx.wear.complications.data.IdAndComplicationData? getIdAndData();
+    method public androidx.wear.complications.data.ComplicationData? getData();
     method public boolean isHighlighted();
     method @UiThread public void onAttach(androidx.wear.watchface.Complication complication);
-    method @UiThread public void onDetach();
-    method @UiThread public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
-    method public void setIdAndData(androidx.wear.complications.data.IdAndComplicationData? idAndComplicationData, boolean loadDrawablesAsynchronous);
+    method @UiThread public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters, int complicationId);
+    method public void setData(androidx.wear.complications.data.ComplicationData? complicationData, boolean loadDrawablesAsynchronous);
     method public void setIsHighlighted(boolean p);
     property public abstract boolean isHighlighted;
   }
 
   public class CanvasComplicationDrawable implements androidx.wear.watchface.CanvasComplication {
     ctor public CanvasComplicationDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable, androidx.wear.watchface.WatchState watchState);
-    method public void clearIdAndData();
     method public void drawOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, @ColorInt int color);
+    method public androidx.wear.complications.data.ComplicationData? getData();
     method public final androidx.wear.watchface.complications.rendering.ComplicationDrawable getDrawable();
-    method public androidx.wear.complications.data.IdAndComplicationData? getIdAndData();
     method @UiThread public boolean isHighlighted();
     method public void onAttach(androidx.wear.watchface.Complication complication);
-    method public void onDetach();
-    method public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
+    method public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters, int complicationId);
+    method public void setData(androidx.wear.complications.data.ComplicationData? complicationData, boolean loadDrawablesAsynchronous);
     method public final void setDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable value);
-    method public void setIdAndData(androidx.wear.complications.data.IdAndComplicationData? idAndComplicationData, boolean loadDrawablesAsynchronous);
     method @UiThread public void setIsHighlighted(boolean value);
     property public final androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable;
     property @UiThread public boolean isHighlighted;
@@ -33,14 +29,14 @@
   public final class Complication {
     method public android.graphics.Rect computeBounds(android.graphics.Rect screen);
     method public static androidx.wear.watchface.Complication.Builder createBackgroundComplicationBuilder(int id, androidx.wear.watchface.CanvasComplication renderer, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy);
-    method public static androidx.wear.watchface.Complication.Builder createRoundRectComplicationBuilder(int id, androidx.wear.watchface.CanvasComplication renderer, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy, androidx.wear.complications.ComplicationBounds complicationBounds);
+    method public static androidx.wear.watchface.Complication.Builder createRoundRectComplicationBuilder(int id, androidx.wear.watchface.CanvasComplication renderer, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy, androidx.wear.complications.ComplicationBounds bounds);
     method public int getBoundsType();
     method @UiThread public androidx.wear.complications.ComplicationBounds getComplicationBounds();
-    method public android.os.Bundle? getComplicationConfigExtras();
     method public androidx.wear.watchface.ObservableWatchData<androidx.wear.complications.data.ComplicationData> getComplicationData();
+    method public android.os.Bundle getConfigExtras();
     method @UiThread public androidx.wear.complications.DefaultComplicationProviderPolicy getDefaultProviderPolicy();
     method @UiThread public androidx.wear.complications.data.ComplicationType getDefaultProviderType();
-    method @UiThread public androidx.wear.watchface.CanvasComplication getRenderer();
+    method public androidx.wear.watchface.CanvasComplication getRenderer();
     method @UiThread public java.util.List<androidx.wear.complications.data.ComplicationType> getSupportedTypes();
     method public void invalidate();
     method public boolean isActiveAt(long dateTimeMillis);
@@ -48,25 +44,23 @@
     method public boolean isFixedComplicationProvider();
     method public boolean isInitiallyEnabled();
     method @UiThread public void render(android.graphics.Canvas canvas, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
-    method @UiThread public void setComplicationBounds(androidx.wear.complications.ComplicationBounds value);
-    method @UiThread public void setRenderer(androidx.wear.watchface.CanvasComplication value);
     property public final int boundsType;
     property @UiThread public final androidx.wear.complications.ComplicationBounds complicationBounds;
-    property public final android.os.Bundle? complicationConfigExtras;
     property public final androidx.wear.watchface.ObservableWatchData<androidx.wear.complications.data.ComplicationData> complicationData;
+    property public final android.os.Bundle configExtras;
     property @UiThread public final androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy;
     property @UiThread public final androidx.wear.complications.data.ComplicationType defaultProviderType;
     property @UiThread public final boolean enabled;
     property public final boolean fixedComplicationProvider;
     property public final boolean initiallyEnabled;
-    property @UiThread public final androidx.wear.watchface.CanvasComplication renderer;
+    property public final androidx.wear.watchface.CanvasComplication renderer;
     property @UiThread public final java.util.List<androidx.wear.complications.data.ComplicationType> supportedTypes;
     field public static final androidx.wear.watchface.Complication.Companion Companion;
   }
 
   public static final class Complication.Builder {
     method public androidx.wear.watchface.Complication build();
-    method public androidx.wear.watchface.Complication.Builder setComplicationConfigExtras(android.os.Bundle? extras);
+    method public androidx.wear.watchface.Complication.Builder setConfigExtras(android.os.Bundle extras);
     method public androidx.wear.watchface.Complication.Builder setDefaultProviderType(androidx.wear.complications.data.ComplicationType defaultProviderType);
     method public androidx.wear.watchface.Complication.Builder setEnabled(boolean enabled);
     method public androidx.wear.watchface.Complication.Builder setFixedComplicationProvider(boolean fixedComplicationProvider);
@@ -74,17 +68,17 @@
 
   public static final class Complication.Companion {
     method public androidx.wear.watchface.Complication.Builder createBackgroundComplicationBuilder(int id, androidx.wear.watchface.CanvasComplication renderer, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy);
-    method public androidx.wear.watchface.Complication.Builder createRoundRectComplicationBuilder(int id, androidx.wear.watchface.CanvasComplication renderer, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy, androidx.wear.complications.ComplicationBounds complicationBounds);
+    method public androidx.wear.watchface.Complication.Builder createRoundRectComplicationBuilder(int id, androidx.wear.watchface.CanvasComplication renderer, java.util.List<? extends androidx.wear.complications.data.ComplicationType> supportedTypes, androidx.wear.complications.DefaultComplicationProviderPolicy defaultProviderPolicy, androidx.wear.complications.ComplicationBounds bounds);
   }
 
   public final class ComplicationOutlineRenderer {
     ctor public ComplicationOutlineRenderer();
-    method public static void drawComplicationSelectOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, @ColorInt int color);
+    method public static void drawComplicationOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, @ColorInt int color);
     field public static final androidx.wear.watchface.ComplicationOutlineRenderer.Companion Companion;
   }
 
   public static final class ComplicationOutlineRenderer.Companion {
-    method public void drawComplicationSelectOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, @ColorInt int color);
+    method public void drawComplicationOutline(android.graphics.Canvas canvas, android.graphics.Rect bounds, @ColorInt int color);
   }
 
   public final class ComplicationsManager {
@@ -93,14 +87,14 @@
     method @UiThread public void bringAttentionToComplication(int complicationId);
     method public operator androidx.wear.watchface.Complication? get(int id);
     method public androidx.wear.watchface.Complication? getBackgroundComplication();
-    method public androidx.wear.watchface.Complication? getComplicationAt(int x, int y);
+    method public androidx.wear.watchface.Complication? getComplicationAt(@Px int x, @Px int y);
     method public java.util.Map<java.lang.Integer,androidx.wear.watchface.Complication> getComplications();
     method @UiThread public void removeTapListener(androidx.wear.watchface.ComplicationsManager.TapCallback tapCallback);
     property public final java.util.Map<java.lang.Integer,androidx.wear.watchface.Complication> complications;
   }
 
   public static interface ComplicationsManager.TapCallback {
-    method public default void onComplicationSingleTapped(int complicationId);
+    method public default void onComplicationTapped(int complicationId);
   }
 
   public final class ComplicationsManagerKt {
@@ -114,11 +108,13 @@
   }
 
   public final class GlesTextureComplication {
-    ctor public GlesTextureComplication(androidx.wear.watchface.CanvasComplication canvasComplication, @Px int textureWidth, @Px int textureHeight, int textureType);
+    ctor public GlesTextureComplication(androidx.wear.watchface.CanvasComplication canvasComplication, @Px int textureWidth, @Px int textureHeight, int textureType, int id);
     method public void bind();
     method public androidx.wear.watchface.CanvasComplication getCanvasComplication();
+    method public int getId();
     method public void renderToTexture(android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
     property public final androidx.wear.watchface.CanvasComplication canvasComplication;
+    property public final int id;
   }
 
   public enum LayerMode {
@@ -182,12 +178,12 @@
     ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public RenderParameters(androidx.wear.watchface.data.RenderParametersWireFormat wireFormat);
     method public androidx.wear.watchface.DrawMode getDrawMode();
     method public java.util.Map<androidx.wear.watchface.style.Layer,androidx.wear.watchface.LayerMode> getLayerParameters();
-    method public int getOutlineTint();
+    method @ColorInt public int getOutlineTint();
     method public Integer? getSelectedComplicationId();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.wear.watchface.data.RenderParametersWireFormat toWireFormat();
     property public final androidx.wear.watchface.DrawMode drawMode;
     property public final java.util.Map<androidx.wear.watchface.style.Layer,androidx.wear.watchface.LayerMode> layerParameters;
-    property public final int outlineTint;
+    property @ColorInt public final int outlineTint;
     property public final Integer? selectedComplicationId;
     field public static final androidx.wear.watchface.RenderParameters.Companion Companion;
     field public static final androidx.wear.watchface.RenderParameters DEFAULT_INTERACTIVE;
@@ -220,17 +216,27 @@
   }
 
   public abstract static class Renderer.CanvasRenderer extends androidx.wear.watchface.Renderer {
-    ctor public Renderer.CanvasRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, int canvasType, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis);
+    ctor public Renderer.CanvasRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, int canvasType, @IntRange(from=0, to=60000) long interactiveDrawModeUpdateDelayMillis);
     method @UiThread public abstract void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar);
   }
 
   public abstract static class Renderer.GlesRenderer extends androidx.wear.watchface.Renderer {
-    ctor public Renderer.GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis, optional int[] eglConfigAttribList, optional int[] eglSurfaceAttribList);
-    ctor public Renderer.GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis, optional int[] eglConfigAttribList);
-    ctor public Renderer.GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=10000) long interactiveDrawModeUpdateDelayMillis);
+    ctor public Renderer.GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=60000) long interactiveDrawModeUpdateDelayMillis, optional int[] eglConfigAttribList, optional int[] eglSurfaceAttribList);
+    ctor public Renderer.GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=60000) long interactiveDrawModeUpdateDelayMillis, optional int[] eglConfigAttribList);
+    ctor public Renderer.GlesRenderer(android.view.SurfaceHolder surfaceHolder, androidx.wear.watchface.style.UserStyleRepository userStyleRepository, androidx.wear.watchface.WatchState watchState, @IntRange(from=0, to=60000) long interactiveDrawModeUpdateDelayMillis);
+    method public final android.opengl.EGLConfig getEglConfig();
+    method public final android.opengl.EGLContext? getEglContext();
+    method public final android.opengl.EGLDisplay? getEglDisplay();
+    method @UiThread public final void initOpenGlContext();
     method @UiThread public void onGlContextCreated();
-    method @UiThread public void onGlSurfaceCreated(int width, int height);
+    method @UiThread public void onGlSurfaceCreated(@Px int width, @Px int height);
     method @UiThread public abstract void render(android.icu.util.Calendar calendar);
+    method public final void setEglConfig(android.opengl.EGLConfig p);
+    method public final void setEglContext(android.opengl.EGLContext? p);
+    method public final void setEglDisplay(android.opengl.EGLDisplay? p);
+    property public final android.opengl.EGLConfig eglConfig;
+    property public final android.opengl.EGLContext? eglContext;
+    property public final android.opengl.EGLDisplay? eglDisplay;
   }
 
   public final class RendererKt {
diff --git a/wear/wear-watchface/guava/build.gradle b/wear/wear-watchface/guava/build.gradle
index 642c937..a70be6e 100644
--- a/wear/wear-watchface/guava/build.gradle
+++ b/wear/wear-watchface/guava/build.gradle
@@ -31,6 +31,12 @@
     api(project(":wear:wear-watchface"))
     api(GUAVA_LISTENABLE_FUTURE)
 
+    androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
+    androidTestImplementation(ANDROIDX_TEST_CORE)
+    androidTestImplementation(ANDROIDX_TEST_RUNNER)
+    androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+    androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+    androidTestImplementation(TRUTH)
     testImplementation(ANDROIDX_TEST_CORE)
     testImplementation(ANDROIDX_TEST_RUNNER)
     testImplementation(ANDROIDX_TEST_RULES)
diff --git a/wear/wear-watchface/guava/src/androidTest/java/AsyncListenableWatchFaceServiceTest.kt b/wear/wear-watchface/guava/src/androidTest/java/AsyncListenableWatchFaceServiceTest.kt
new file mode 100644
index 0000000..1736917
--- /dev/null
+++ b/wear/wear-watchface/guava/src/androidTest/java/AsyncListenableWatchFaceServiceTest.kt
@@ -0,0 +1,110 @@
+/*
+ * 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 android.graphics.Canvas
+import android.graphics.Rect
+import android.icu.util.Calendar
+import android.os.Handler
+import android.os.Looper
+import android.view.SurfaceHolder
+import androidx.wear.watchface.CanvasType
+import androidx.wear.watchface.ListenableWatchFaceService
+import androidx.wear.watchface.MutableWatchState
+import androidx.wear.watchface.Renderer
+import androidx.wear.watchface.WatchFace
+import androidx.wear.watchface.WatchFaceType
+import androidx.wear.watchface.WatchState
+import androidx.wear.watchface.style.UserStyleRepository
+import androidx.wear.watchface.style.UserStyleSchema
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.ListenableFuture
+import com.google.common.util.concurrent.SettableFuture
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.mockito.Mockito
+
+private const val REFERENCE_PREVIEW_TIME = 123456L
+
+private class FakeRenderer(
+    surfaceHolder: SurfaceHolder,
+    watchState: WatchState,
+    userStyleRepository: UserStyleRepository
+) : Renderer.CanvasRenderer(
+    surfaceHolder,
+    userStyleRepository,
+    watchState,
+    CanvasType.SOFTWARE,
+    16
+) {
+    override fun render(canvas: Canvas, bounds: Rect, calendar: Calendar) {
+    }
+}
+
+private class TestAsyncListenableWatchFaceService(private val handler: Handler) :
+    ListenableWatchFaceService() {
+    override fun createWatchFaceFuture(
+        surfaceHolder: SurfaceHolder,
+        watchState: WatchState
+    ): ListenableFuture<WatchFace> {
+        val future = SettableFuture.create<WatchFace>()
+        val userStyleRepository = UserStyleRepository(
+            UserStyleSchema(emptyList())
+        )
+        // Post a task to resolve the future.
+        handler.post {
+            future.set(
+                WatchFace(
+                    WatchFaceType.DIGITAL,
+                    userStyleRepository,
+                    FakeRenderer(surfaceHolder, watchState, userStyleRepository)
+                ).apply { setOverridePreviewReferenceTimeMillis(REFERENCE_PREVIEW_TIME) }
+            )
+        }
+        return future
+    }
+
+    suspend fun createWatchFaceForTest(
+        surfaceHolder: SurfaceHolder,
+        watchState: WatchState
+    ): WatchFace = createWatchFace(surfaceHolder, watchState)
+}
+
+/**
+ * Illustrates that createWatchFaceFuture can be resolved in a different task posted to the main
+ * looper.
+ */
+public class AsyncListenableWatchFaceServiceTest {
+
+    @Test
+    public fun asyncTest() {
+        val handler = Handler(Looper.getMainLooper())
+        val service = TestAsyncListenableWatchFaceService(handler)
+        val mockSurfaceHolder = Mockito.mock(SurfaceHolder::class.java)
+        Mockito.`when`(mockSurfaceHolder.surfaceFrame).thenReturn(Rect(0, 0, 100, 100))
+
+        runBlocking {
+            val watchFace = service.createWatchFaceForTest(
+                mockSurfaceHolder,
+                MutableWatchState().asWatchState()
+            )
+
+            // Simple check that [watchFace] looks sensible.
+            assertThat(watchFace.overridePreviewReferenceTimeMillis).isEqualTo(
+                REFERENCE_PREVIEW_TIME
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/wear/wear-watchface/guava/src/test/java/androidx/wear/watchface/ListenableWatchFaceServiceTest.kt b/wear/wear-watchface/guava/src/test/java/androidx/wear/watchface/ListenableWatchFaceServiceTest.kt
index 095e53f..272465f 100644
--- a/wear/wear-watchface/guava/src/test/java/androidx/wear/watchface/ListenableWatchFaceServiceTest.kt
+++ b/wear/wear-watchface/guava/src/test/java/androidx/wear/watchface/ListenableWatchFaceServiceTest.kt
@@ -98,4 +98,4 @@
         )
             .doNotInstrumentPackage("androidx.wear.watchface")
             .build()
-}
\ No newline at end of file
+}
diff --git a/wear/wear-watchface/samples/src/main/AndroidManifest.xml b/wear/wear-watchface/samples/src/main/AndroidManifest.xml
index 1bf118d..202b8d4 100644
--- a/wear/wear-watchface/samples/src/main/AndroidManifest.xml
+++ b/wear/wear-watchface/samples/src/main/AndroidManifest.xml
@@ -135,6 +135,40 @@
                 android:resource="@xml/watch_face" />
         </service>
 
+        <service
+            android:name=".ExampleOpenGLBackgroundInitWatchFaceService"
+            android:directBootAware="true"
+            android:exported="true"
+            android:label="@string/gl_background_init_watch_face_name"
+            android:permission="android.permission.BIND_WALLPAPER">
+            <intent-filter>
+                <action android:name="android.service.wallpaper.WallpaperService" />
+                <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
+            </intent-filter>
+
+            <intent-filter>
+                <action android:name="com.google.android.wearable.libraries.steampack.watchface.MockTime" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="text/plain" />
+            </intent-filter>
+
+            <meta-data
+                android:name="com.google.android.wearable.standalone"
+                android:value="true" />
+            <meta-data
+                android:name="com.google.android.wearable.watchface.preview"
+                android:resource="@drawable/watch_preview" />
+            <meta-data
+                android:name="com.google.android.wearable.watchface.preview_circular"
+                android:resource="@drawable/watch_preview" />
+            <meta-data
+                android:name="com.google.android.wearable.watchface.wearableConfigurationAction"
+                android:value="androidx.wear.watchface.editor.action.WATCH_FACE_EDITOR"/>
+            <meta-data
+                android:name="android.service.wallpaper"
+                android:resource="@xml/watch_face" />
+        </service>
+
         <activity
             android:name="androidx.wear.complications.ComplicationHelperActivity"
             android:exported="false" />
diff --git a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasAnalogWatchFaceService.kt b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasAnalogWatchFaceService.kt
index 90b7e51..9ffabd80 100644
--- a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasAnalogWatchFaceService.kt
+++ b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasAnalogWatchFaceService.kt
@@ -30,6 +30,7 @@
 import androidx.wear.complications.DefaultComplicationProviderPolicy
 import androidx.wear.complications.SystemProviders
 import androidx.wear.complications.data.ComplicationType
+import androidx.wear.watchface.CanvasComplicationDrawable
 import androidx.wear.watchface.CanvasType
 import androidx.wear.watchface.Complication
 import androidx.wear.watchface.ComplicationsManager
@@ -218,7 +219,7 @@
     )
     val leftComplication = Complication.createRoundRectComplicationBuilder(
         EXAMPLE_CANVAS_WATCHFACE_LEFT_COMPLICATION_ID,
-        watchFaceStyle.getComplicationDrawableRenderer(context, watchState),
+        CanvasComplicationDrawable(watchFaceStyle.getDrawable(context)!!, watchState),
         listOf(
             ComplicationType.RANGED_VALUE,
             ComplicationType.LONG_TEXT,
@@ -232,7 +233,7 @@
         .build()
     val rightComplication = Complication.createRoundRectComplicationBuilder(
         EXAMPLE_CANVAS_WATCHFACE_RIGHT_COMPLICATION_ID,
-        watchFaceStyle.getComplicationDrawableRenderer(context, watchState),
+        CanvasComplicationDrawable(watchFaceStyle.getDrawable(context)!!, watchState),
         listOf(
             ComplicationType.RANGED_VALUE,
             ComplicationType.LONG_TEXT,
@@ -272,7 +273,7 @@
     private val context: Context,
     private var watchFaceColorStyle: WatchFaceColorStyle,
     userStyleRepository: UserStyleRepository,
-    private val watchState: WatchState,
+    watchState: WatchState,
     private val colorStyleSetting: ListUserStyleSetting,
     private val drawPipsStyleSetting: BooleanUserStyleSetting,
     private val watchHandLengthStyleSettingDouble: DoubleRangeUserStyleSetting,
@@ -328,8 +329,8 @@
                     // the styles are defined in XML so we need to replace the complication's
                     // drawables.
                     for ((_, complication) in complicationsManager.complications) {
-                        complication.renderer =
-                            watchFaceColorStyle.getComplicationDrawableRenderer(context, watchState)
+                        (complication.renderer as CanvasComplicationDrawable).drawable =
+                            watchFaceColorStyle.getDrawable(context)!!
                     }
 
                     val drawPipsOption = userStyle[drawPipsStyleSetting]?.toBooleanOption()!!
diff --git a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasDigitalWatchFaceService.kt b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasDigitalWatchFaceService.kt
index bfbeb06..21f17bb 100644
--- a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasDigitalWatchFaceService.kt
+++ b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleCanvasDigitalWatchFaceService.kt
@@ -43,6 +43,7 @@
 import androidx.wear.complications.DefaultComplicationProviderPolicy
 import androidx.wear.complications.SystemProviders
 import androidx.wear.complications.data.ComplicationType
+import androidx.wear.watchface.CanvasComplicationDrawable
 import androidx.wear.watchface.CanvasType
 import androidx.wear.watchface.Complication
 import androidx.wear.watchface.ComplicationsManager
@@ -498,7 +499,7 @@
         )
         val leftComplication = Complication.createRoundRectComplicationBuilder(
             ComplicationID.LEFT.ordinal,
-            watchFaceStyle.getComplicationDrawableRenderer(this, watchState),
+            CanvasComplicationDrawable(watchFaceStyle.getDrawable(this)!!, watchState),
             listOf(
                 ComplicationType.RANGED_VALUE,
                 ComplicationType.SHORT_TEXT,
@@ -516,7 +517,7 @@
             .build()
         val rightComplication = Complication.createRoundRectComplicationBuilder(
             ComplicationID.RIGHT.ordinal,
-            watchFaceStyle.getComplicationDrawableRenderer(this, watchState),
+            CanvasComplicationDrawable(watchFaceStyle.getDrawable(this)!!, watchState),
             listOf(
                 ComplicationType.RANGED_VALUE,
                 ComplicationType.SHORT_TEXT,
@@ -543,7 +544,7 @@
         // The upper and lower complications change shape depending on the complication's type.
         val upperComplication = Complication.createRoundRectComplicationBuilder(
             ComplicationID.UPPER.ordinal,
-            watchFaceStyle.getComplicationDrawableRenderer(this, watchState),
+            CanvasComplicationDrawable(watchFaceStyle.getDrawable(this)!!, watchState),
             upperAndLowerComplicationTypes,
             DefaultComplicationProviderPolicy(SystemProviders.WORLD_CLOCK),
             ComplicationBounds(
@@ -565,7 +566,7 @@
             .build()
         val lowerComplication = Complication.createRoundRectComplicationBuilder(
             ComplicationID.LOWER.ordinal,
-            watchFaceStyle.getComplicationDrawableRenderer(this, watchState),
+            CanvasComplicationDrawable(watchFaceStyle.getDrawable(this)!!, watchState),
             upperAndLowerComplicationTypes,
             DefaultComplicationProviderPolicy(SystemProviders.NEXT_EVENT),
             ComplicationBounds(
@@ -587,7 +588,7 @@
             .build()
         val backgroundComplication = Complication.createBackgroundComplicationBuilder(
             ComplicationID.BACKGROUND.ordinal,
-            watchFaceStyle.getComplicationDrawableRenderer(this, watchState),
+            CanvasComplicationDrawable(watchFaceStyle.getDrawable(this)!!, watchState),
             listOf(ComplicationType.PHOTO_IMAGE),
             DefaultComplicationProviderPolicy()
         ).build()
@@ -634,7 +635,7 @@
     private val context: Context,
     private var watchFaceColorStyle: WatchFaceColorStyle,
     userStyleRepository: UserStyleRepository,
-    private val watchState: WatchState,
+    watchState: WatchState,
     private val colorStyleSetting: UserStyleSetting.ListUserStyleSetting,
     private val complicationsManager: ComplicationsManager
 ) : Renderer.CanvasRenderer(
@@ -759,8 +760,8 @@
                     // the styles are defined in XML so we need to replace the complication's
                     // drawables.
                     for ((_, complication) in complicationsManager.complications) {
-                        complication.renderer =
-                            watchFaceColorStyle.getComplicationDrawableRenderer(context, watchState)
+                        (complication.renderer as CanvasComplicationDrawable).drawable =
+                            watchFaceColorStyle.getDrawable(context)!!
                     }
 
                     clearDigitBitmapCache()
diff --git a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleOpenGLBackgroundInitWatchFaceService.kt b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleOpenGLBackgroundInitWatchFaceService.kt
new file mode 100644
index 0000000..c6e47e1
--- /dev/null
+++ b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleOpenGLBackgroundInitWatchFaceService.kt
@@ -0,0 +1,318 @@
+/*
+ * 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.watchface.samples
+
+import android.graphics.BitmapFactory
+import android.icu.util.Calendar
+import android.opengl.EGL14
+import android.opengl.GLES20
+import android.opengl.GLUtils
+import android.opengl.Matrix
+import android.os.Handler
+import android.os.HandlerThread
+import android.view.SurfaceHolder
+import androidx.wear.watchface.Renderer
+import androidx.wear.watchface.WatchFace
+import androidx.wear.watchface.WatchFaceService
+import androidx.wear.watchface.WatchFaceType
+import androidx.wear.watchface.WatchState
+import androidx.wear.watchface.style.UserStyleRepository
+import androidx.wear.watchface.style.UserStyleSchema
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.android.asCoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+/** Expected frame rate in interactive mode.  */
+private const val FPS: Long = 60
+
+/** How long each frame is displayed at expected frame rate.  */
+private const val FRAME_PERIOD_MS: Long = 1000 / FPS
+
+/**
+ * Sample watch face using OpenGL with textures loaded on a background thread by [createWatchFace]
+ * which are used for rendering on the main thread.
+ */
+class ExampleOpenGLBackgroundInitWatchFaceService() : WatchFaceService() {
+    // The CoroutineScope upon which we want to load our textures.
+    private val backgroundThreadCoroutineScope = CoroutineScope(
+        Handler(HandlerThread("BackgroundInit").apply { start() }.looper).asCoroutineDispatcher()
+    )
+
+    override suspend fun createWatchFace(
+        surfaceHolder: SurfaceHolder,
+        watchState: WatchState
+    ): WatchFace {
+        val styleRepository = UserStyleRepository(UserStyleSchema(emptyList()))
+
+        // Create the renderer on the main thread. It's EGLContext is bound to this thread.
+        val renderer = MainThreadRenderer(surfaceHolder, styleRepository, watchState)
+        renderer.initOpenGlContext()
+
+        // Load the textures on a background thread.
+        return withContext(backgroundThreadCoroutineScope.coroutineContext) {
+            // Create a context for the background thread.
+            val backgroundThreadContext = EGL14.eglCreateContext(
+                renderer.eglDisplay,
+                renderer.eglConfig,
+                renderer.eglContext,
+                intArrayOf(
+                    EGL14.EGL_CONTEXT_CLIENT_VERSION,
+                    2,
+                    EGL14.EGL_NONE
+                ),
+                0
+            )
+
+            // Create a 1x1 surface which is needed by EGL14.eglMakeCurrent.
+            val backgroundSurface = EGL14.eglCreatePbufferSurface(
+                renderer.eglDisplay,
+                renderer.eglConfig,
+                intArrayOf(
+                    EGL14.EGL_WIDTH,
+                    1,
+                    EGL14.EGL_HEIGHT,
+                    1,
+                    EGL14.EGL_TEXTURE_TARGET,
+                    EGL14.EGL_NO_TEXTURE,
+                    EGL14.EGL_TEXTURE_FORMAT,
+                    EGL14.EGL_NO_TEXTURE,
+                    EGL14.EGL_NONE
+                ),
+                0
+            )
+
+            EGL14.eglMakeCurrent(
+                renderer.eglDisplay,
+                backgroundSurface,
+                backgroundSurface,
+                backgroundThreadContext
+            )
+
+            // Load our textures.
+            renderer.watchBodyTexture = loadTextureFromResource(R.drawable.wf_background)
+            checkGLError("Load watchBodyTexture")
+            renderer.watchHandTexture = loadTextureFromResource(R.drawable.hand)
+            checkGLError("Load watchHandTexture")
+
+            WatchFace(
+                WatchFaceType.ANALOG,
+                styleRepository,
+                renderer
+            )
+        }
+    }
+
+    fun checkGLError(op: String) {
+        var error: Int
+        while (GLES20.glGetError().also { error = it } != GLES20.GL_NO_ERROR) {
+            System.err.println("OpenGL Error $op: glError $error")
+        }
+    }
+
+    private fun loadTextureFromResource(resourceId: Int): Int {
+        val textureHandle = IntArray(1)
+        GLES20.glGenTextures(1, textureHandle, 0)
+        if (textureHandle[0] != 0) {
+            val bitmap = BitmapFactory.decodeResource(
+                resources,
+                resourceId,
+                BitmapFactory.Options().apply {
+                    inScaled = false // No pre-scaling
+                }
+            )
+            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0])
+            GLES20.glTexParameteri(
+                GLES20.GL_TEXTURE_2D,
+                GLES20.GL_TEXTURE_MIN_FILTER,
+                GLES20.GL_NEAREST
+            )
+            GLES20.glTexParameteri(
+                GLES20.GL_TEXTURE_2D,
+                GLES20.GL_TEXTURE_MAG_FILTER,
+                GLES20.GL_NEAREST
+            )
+            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0)
+            bitmap.recycle()
+        }
+        return textureHandle[0]
+    }
+}
+
+internal class MainThreadRenderer(
+    surfaceHolder: SurfaceHolder,
+    userStyleRepository: UserStyleRepository,
+    watchState: WatchState
+) : Renderer.GlesRenderer(surfaceHolder, userStyleRepository, watchState, FRAME_PERIOD_MS) {
+
+    internal var watchBodyTexture: Int = -1
+    internal var watchHandTexture: Int = -1
+
+    private val projectionMatrix = FloatArray(16)
+    private val viewMatrix = FloatArray(16).apply {
+        Matrix.setLookAtM(
+            this,
+            0,
+            0f,
+            0f,
+            -1.0f,
+            0f,
+            0f,
+            0f,
+            0f,
+            1f,
+            0f
+        )
+    }
+    private val handPositionMatrix = FloatArray(16)
+    private val handViewMatrix = FloatArray(16)
+    private val vpMatrix = FloatArray(16)
+
+    private lateinit var triangleTextureProgram: Gles2TexturedTriangleList.Program
+    private lateinit var backgroundQuad: Gles2TexturedTriangleList
+    private lateinit var secondHandQuad: Gles2TexturedTriangleList
+    private lateinit var minuteHandQuad: Gles2TexturedTriangleList
+    private lateinit var hourHandQuad: Gles2TexturedTriangleList
+
+    override fun onGlContextCreated() {
+        triangleTextureProgram = Gles2TexturedTriangleList.Program()
+        backgroundQuad = createTexturedQuad(
+            triangleTextureProgram, -10f, -10f, 20f, 20f
+        )
+        secondHandQuad = createTexturedQuad(
+            triangleTextureProgram, -0.75f, -6f, 1.5f, 8f
+        )
+        minuteHandQuad = createTexturedQuad(
+            triangleTextureProgram, -0.33f, -4.5f, 0.66f, 6f
+        )
+        hourHandQuad = createTexturedQuad(
+            triangleTextureProgram, -0.25f, -3f, 0.5f, 4f
+        )
+    }
+
+    override fun onGlSurfaceCreated(width: Int, height: Int) {
+        GLES20.glEnable(GLES20.GL_TEXTURE_2D)
+
+        // Update the projection matrix based on the new aspect ratio.
+        val aspectRatio = width.toFloat() / height.toFloat()
+        Matrix.frustumM(
+            projectionMatrix,
+            0 /* offset */,
+            -aspectRatio /* left */,
+            aspectRatio /* right */,
+            -1f /* bottom */,
+            1f /* top */,
+            0.1f /* near */,
+            100f /* far */
+        )
+    }
+
+    private fun createTexturedQuad(
+        program: Gles2TexturedTriangleList.Program,
+        left: Float,
+        top: Float,
+        width: Float,
+        height: Float
+    ) = Gles2TexturedTriangleList(
+        program,
+        floatArrayOf(
+            top + 0f,
+            left + 0.0f,
+            0.0f,
+
+            top + 0.0f,
+            left + width,
+            0.0f,
+
+            top + height,
+            left + 0.0f,
+            0.0f,
+
+            top + 0.0f,
+            left + width,
+            0.0f,
+
+            top + height,
+            left + 0.0f,
+            0.0f,
+
+            top + height,
+            left + width,
+            0.0f
+        ),
+        floatArrayOf(
+            1.0f,
+            1.0f,
+
+            1.0f,
+            0.0f,
+
+            0.0f,
+            1.0f,
+
+            1.0f,
+            0.0f,
+
+            0.0f,
+            1.0f,
+
+            0.0f,
+            0.0f
+        )
+    )
+
+    override fun render(calendar: Calendar) {
+        GLES20.glClearColor(0f, 1f, 0f, 1f)
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
+
+        triangleTextureProgram.bindProgramAndAttribs()
+        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, watchBodyTexture)
+
+        GLES20.glEnable(GLES20.GL_BLEND)
+        GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ZERO)
+
+        Matrix.multiplyMM(vpMatrix, 0, projectionMatrix, 0, viewMatrix, 0)
+        backgroundQuad.draw(vpMatrix)
+
+        GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA)
+        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, watchHandTexture)
+
+        val hours = calendar.get(Calendar.HOUR).toFloat()
+        val minutes = calendar.get(Calendar.MINUTE).toFloat()
+        val seconds = calendar.get(Calendar.SECOND).toFloat() +
+            (calendar.get(Calendar.MILLISECOND).toFloat() / 1000f)
+
+        val secondsRot = seconds / 60.0f * 360.0f
+        Matrix.setRotateM(handPositionMatrix, 0, secondsRot, 0f, 0f, 1f)
+        Matrix.multiplyMM(handViewMatrix, 0, viewMatrix, 0, handPositionMatrix, 0)
+        Matrix.multiplyMM(vpMatrix, 0, projectionMatrix, 0, handViewMatrix, 0)
+        secondHandQuad.draw(vpMatrix)
+
+        val minuteRot = (minutes + seconds / 60.0f) / 60.0f * 360.0f
+        Matrix.setRotateM(handPositionMatrix, 0, minuteRot, 0f, 0f, 1f)
+        Matrix.multiplyMM(handViewMatrix, 0, viewMatrix, 0, handPositionMatrix, 0)
+        Matrix.multiplyMM(vpMatrix, 0, projectionMatrix, 0, handViewMatrix, 0)
+        minuteHandQuad.draw(vpMatrix)
+
+        val hourRot = (hours + minutes / 60.0f + seconds / 3600.0f) / 12.0f * 360.0f
+        Matrix.setRotateM(handPositionMatrix, 0, hourRot, 0f, 0f, 1f)
+        Matrix.multiplyMM(handViewMatrix, 0, viewMatrix, 0, handPositionMatrix, 0)
+        Matrix.multiplyMM(vpMatrix, 0, projectionMatrix, 0, handViewMatrix, 0)
+        hourHandQuad.draw(vpMatrix)
+
+        triangleTextureProgram.unbindAttribs()
+    }
+}
diff --git a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleOpenGLWatchFaceService.kt b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleOpenGLWatchFaceService.kt
index 42f713f..21a2e63 100644
--- a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleOpenGLWatchFaceService.kt
+++ b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleOpenGLWatchFaceService.kt
@@ -31,6 +31,7 @@
 import androidx.wear.complications.DefaultComplicationProviderPolicy
 import androidx.wear.complications.SystemProviders
 import androidx.wear.complications.data.ComplicationType
+import androidx.wear.watchface.CanvasComplicationDrawable
 import androidx.wear.watchface.Complication
 import androidx.wear.watchface.ComplicationsManager
 import androidx.wear.watchface.DrawMode
@@ -114,7 +115,7 @@
         listOf(
             Complication.createRoundRectComplicationBuilder(
                 EXAMPLE_OPENGL_COMPLICATION_ID,
-                watchFaceStyle.getComplicationDrawableRenderer(context, watchState),
+                CanvasComplicationDrawable(watchFaceStyle.getDrawable(context)!!, watchState),
                 listOf(
                     ComplicationType.RANGED_VALUE,
                     ComplicationType.LONG_TEXT,
@@ -136,6 +137,7 @@
         colorStyleSetting,
         complicationsManager[EXAMPLE_OPENGL_COMPLICATION_ID]!!
     )
+    renderer.initOpenGlContext()
     return WatchFace(
         WatchFaceType.ANALOG,
         userStyleRepository,
@@ -326,7 +328,8 @@
             complication.renderer,
             128,
             128,
-            GLES20.GL_TEXTURE_2D
+            GLES20.GL_TEXTURE_2D,
+            EXAMPLE_OPENGL_COMPLICATION_ID
         )
     }
 
diff --git a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/Style.kt b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/Style.kt
index 1103c56..c0c0d0e 100644
--- a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/Style.kt
+++ b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/Style.kt
@@ -19,8 +19,6 @@
 import android.content.Context
 import android.graphics.Color
 import androidx.wear.watchface.complications.rendering.ComplicationDrawable
-import androidx.wear.watchface.CanvasComplicationDrawable
-import androidx.wear.watchface.WatchState
 
 private fun Context.getStyleResourceId(
     styleResourceId: Int,
@@ -88,9 +86,6 @@
         }
     }
 
-    fun getComplicationDrawableRenderer(context: Context, watchState: WatchState) =
-        CanvasComplicationDrawable(
-            ComplicationDrawable.getDrawable(context, complicationResourceId)!!,
-            watchState
-        )
+    fun getDrawable(context: Context) =
+        ComplicationDrawable.getDrawable(context, complicationResourceId)
 }
diff --git a/wear/wear-watchface/samples/src/main/res/drawable/hand.png b/wear/wear-watchface/samples/src/main/res/drawable/hand.png
new file mode 100644
index 0000000..4e89c82
--- /dev/null
+++ b/wear/wear-watchface/samples/src/main/res/drawable/hand.png
Binary files differ
diff --git a/wear/wear-watchface/samples/src/main/res/drawable/wf_background.png b/wear/wear-watchface/samples/src/main/res/drawable/wf_background.png
new file mode 100644
index 0000000..469c28d
--- /dev/null
+++ b/wear/wear-watchface/samples/src/main/res/drawable/wf_background.png
Binary files differ
diff --git a/wear/wear-watchface/samples/src/main/res/values/strings.xml b/wear/wear-watchface/samples/src/main/res/values/strings.xml
index 90f3dd8..49df858 100644
--- a/wear/wear-watchface/samples/src/main/res/values/strings.xml
+++ b/wear/wear-watchface/samples/src/main/res/values/strings.xml
@@ -22,6 +22,8 @@
     <string name="canvas_digital_watch_face_name"
         translatable="false">Example Digital Watchface</string>
     <string name="gl_watch_face_name" translatable="false">Example OpenGL Watchface</string>
+    <string name="gl_background_init_watch_face_name"
+        translatable="false">Background Init Watchface</string>
 
     <!-- Name of watchface style [CHAR LIMIT=20] -->
     <string name="red_style_name">Red Style</string>
diff --git a/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceServiceImageTest.kt b/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceServiceImageTest.kt
index 929dadc..ab591af 100644
--- a/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceServiceImageTest.kt
+++ b/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceServiceImageTest.kt
@@ -194,6 +194,12 @@
 
         engineWrapper =
             canvasAnalogWatchFaceService.onCreateEngine() as WatchFaceService.EngineWrapper
+        engineWrapper.onSurfaceChanged(
+            surfaceHolder,
+            0,
+            surfaceHolder.surfaceFrame.width(),
+            surfaceHolder.surfaceFrame.height()
+        )
     }
 
     private fun initGles2WatchFace() {
@@ -214,6 +220,12 @@
         setPendingWallpaperInteractiveWatchFaceInstance()
 
         engineWrapper = glesWatchFaceService.onCreateEngine() as WatchFaceService.EngineWrapper
+        engineWrapper.onSurfaceChanged(
+            surfaceHolder,
+            0,
+            surfaceHolder.surfaceFrame.width(),
+            surfaceHolder.surfaceFrame.height()
+        )
     }
 
     private fun setPendingWallpaperInteractiveWatchFaceInstance() {
@@ -556,6 +568,12 @@
             )
 
             engineWrapper = service.onCreateEngine() as WatchFaceService.EngineWrapper
+            engineWrapper.onSurfaceChanged(
+                surfaceHolder,
+                0,
+                surfaceHolder.surfaceFrame.width(),
+                surfaceHolder.surfaceFrame.height()
+            )
             handler.post { engineWrapper.draw() }
         }
 
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
index acfc804..aac8b57 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
@@ -23,20 +23,20 @@
 import android.graphics.drawable.Drawable
 import android.icu.util.Calendar
 import android.os.Bundle
-import android.support.wearable.complications.ComplicationData
 import androidx.annotation.ColorInt
 import androidx.annotation.UiThread
 import androidx.wear.complications.ComplicationBounds
 import androidx.wear.complications.ComplicationHelperActivity
 import androidx.wear.complications.DefaultComplicationProviderPolicy
 import androidx.wear.complications.data.ComplicationType
-import androidx.wear.complications.data.IdAndComplicationData
+import androidx.wear.complications.data.ComplicationData
 import androidx.wear.utility.TraceEvent
 import androidx.wear.watchface.complications.rendering.ComplicationDrawable
 import androidx.wear.watchface.data.ComplicationBoundsType
 import androidx.wear.watchface.style.Layer
 import androidx.wear.watchface.style.UserStyleSetting
 import androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting
+import androidx.wear.watchface.style.UserStyleSetting.ComplicationsUserStyleSetting.ComplicationOverlay
 
 /** Interface for rendering complications onto a [Canvas]. */
 public interface CanvasComplication {
@@ -49,14 +49,7 @@
     public fun onAttach(complication: Complication)
 
     /**
-     * Called when the CanvasComplication detaches from a [Complication]. This will get called if
-     * [Complication.renderer] is assigned to a different CanvasComplication.
-     */
-    @UiThread
-    public fun onDetach()
-
-    /**
-     * Draws the complication defined by [getIdAndData] into the canvas with the specified bounds.
+     * Draws the complication defined by [getData] into the canvas with the specified bounds.
      * This will usually be called by user watch face drawing code, but the system may also call it
      * for complication selection UI rendering. The width and height will be the same as that
      * computed by computeBounds but the translation and canvas size may differ.
@@ -65,13 +58,15 @@
      * @param bounds A [Rect] describing the bounds of the complication
      * @param calendar The current [Calendar]
      * @param renderParameters The current [RenderParameters]
+     * @param complicationId The Id of the parent [Complication]
      */
     @UiThread
     public fun render(
         canvas: Canvas,
         bounds: Rect,
         calendar: Calendar,
-        renderParameters: RenderParameters
+        renderParameters: RenderParameters,
+        complicationId: Int
     )
 
     /**
@@ -83,21 +78,18 @@
     @set:JvmName("setIsHighlighted")
     public var isHighlighted: Boolean
 
-    /** Returns the [IdAndComplicationData] to render with. */
-    public fun getIdAndData(): IdAndComplicationData?
+    /** Returns the [ComplicationData] to render with. */
+    public fun getData(): ComplicationData?
 
     /**
-     * Sets the [IdAndComplicationData] to render with.
+     * Sets the [ComplicationData] to render with. Note ComplicationData may reference one or
+     * more [Drawable]s which get loaded as a side effect, you can choose whether this is done
+     * synchronously or asynchronously via [loadDrawablesAsynchronous].
      *
+     * @param complicationData The [ComplicationData] to render with
      * @param loadDrawablesAsynchronous Whether or not any drawables should be loaded asynchronously
-     **/
-    public fun setIdAndData(
-        idAndComplicationData: IdAndComplicationData?,
-        loadDrawablesAsynchronous: Boolean
-    )
-
-    /** The [IdAndComplicationData] should be cleared. */
-    public fun clearIdAndData()
+     */
+    public fun setData(complicationData: ComplicationData?, loadDrawablesAsynchronous: Boolean)
 }
 
 /**
@@ -132,6 +124,9 @@
     /** The [ComplicationDrawable] to render with. */
     public var drawable: ComplicationDrawable = drawable
         set(value) {
+            // Copy the ComplicationData otherwise the complication will be blank until the next
+            // update.
+            value.setComplicationData(field.complicationData, false)
             field = value
             value.isInAmbientMode = watchState.isAmbient.value
             value.isLowBitAmbient = watchState.hasLowBitAmbient
@@ -153,17 +148,12 @@
     }
 
     /** {@inheritDoc} */
-    override fun onDetach() {
-        watchState.isAmbient.removeObserver(isAmbientObserver)
-        attachedComplication = null
-    }
-
-    /** {@inheritDoc} */
     override fun render(
         canvas: Canvas,
         bounds: Rect,
         calendar: Calendar,
-        renderParameters: RenderParameters
+        renderParameters: RenderParameters,
+        complicationId: Int
     ) {
         when (renderParameters.layerParameters[Layer.COMPLICATIONS]) {
             LayerMode.DRAW -> {
@@ -175,8 +165,7 @@
                 drawable.bounds = bounds
                 drawable.currentTimeMillis = calendar.timeInMillis
                 val wasHighlighted = drawable.isHighlighted
-                drawable.isHighlighted =
-                    renderParameters.selectedComplicationId == getIdAndData()?.complicationId
+                drawable.isHighlighted = renderParameters.selectedComplicationId == complicationId
                 drawable.draw(canvas)
                 drawable.isHighlighted = wasHighlighted
 
@@ -200,7 +189,7 @@
         @ColorInt color: Int
     ) {
         if (!attachedComplication!!.fixedComplicationProvider) {
-            ComplicationOutlineRenderer.drawComplicationSelectOutline(
+            ComplicationOutlineRenderer.drawComplicationOutline(
                 canvas,
                 bounds,
                 color
@@ -224,25 +213,20 @@
             drawable.isHighlighted = value
         }
 
-    private var _idAndData: IdAndComplicationData? = null
+    private var _data: ComplicationData? = null
 
-    override fun getIdAndData(): IdAndComplicationData? = _idAndData
+    override fun getData(): ComplicationData? = _data
 
-    override fun setIdAndData(
-        idAndComplicationData: IdAndComplicationData?,
+    override fun setData(
+        complicationData: ComplicationData?,
         loadDrawablesAsynchronous: Boolean
     ): Unit = TraceEvent("CanvasComplicationDrawable.setIdAndData").use {
-        _idAndData = idAndComplicationData
+        _data = complicationData
         drawable.setComplicationData(
-            idAndComplicationData?.complicationData?.asWireComplicationData(),
+            complicationData?.asWireComplicationData(),
             loadDrawablesAsynchronous
         )
     }
-
-    override fun clearIdAndData() {
-        _idAndData = null
-        drawable.setComplicationData(null, false)
-    }
 }
 
 /**
@@ -253,20 +237,24 @@
 public class Complication internal constructor(
     internal val id: Int,
     @ComplicationBoundsType public val boundsType: Int,
-    complicationBounds: ComplicationBounds,
-    canvasComplication: CanvasComplication,
+    bounds: ComplicationBounds,
+    /** The [CanvasComplication] used to render the complication. */
+    public val renderer: CanvasComplication,
     supportedTypes: List<ComplicationType>,
     defaultProviderPolicy: DefaultComplicationProviderPolicy,
     defaultProviderType: ComplicationType,
     /**
-     * The initial state of the complication. Note complications can be enabled / disabled by
-     * [UserStyleSetting.ComplicationsUserStyleSetting].
+     * At creation a complication is either enabled or disabled. This can be overridden by a
+     * [ComplicationsUserStyleSetting] (see [ComplicationOverlay.enabled]).
+     *
+     * Editors need to know the initial state of a complication to predict the effects of making a
+     * style change.
      */
     @get:JvmName("isInitiallyEnabled")
     public val initiallyEnabled: Boolean,
 
     /** Extras to be merged into the Intent sent when invoking the provider chooser activity. */
-    public val complicationConfigExtras: Bundle?,
+    public val configExtras: Bundle,
 
     /** Whether or not the complication provider is fixed. */
     @get:JvmName("isFixedComplicationProvider")
@@ -309,14 +297,14 @@
             defaultProviderPolicy: DefaultComplicationProviderPolicy,
 
             /** The initial [ComplicationBounds]. */
-            complicationBounds: ComplicationBounds
+            bounds: ComplicationBounds
         ): Builder = Builder(
             id,
             renderer,
             supportedTypes,
             defaultProviderPolicy,
             ComplicationBoundsType.ROUND_RECT,
-            complicationBounds
+            bounds
         )
 
         /**
@@ -369,11 +357,11 @@
         private val supportedTypes: List<ComplicationType>,
         private val defaultProviderPolicy: DefaultComplicationProviderPolicy,
         @ComplicationBoundsType private val boundsType: Int,
-        private val complicationBounds: ComplicationBounds
+        private val bounds: ComplicationBounds
     ) {
         private var defaultProviderType = ComplicationType.NOT_CONFIGURED
         private var initiallyEnabled = true
-        private var complicationConfigExtras: Bundle? = null
+        private var configExtras: Bundle = Bundle.EMPTY
         private var fixedComplicationProvider = false
 
         /**
@@ -401,8 +389,8 @@
          * Sets optional extras to be merged into the Intent sent when invoking the provider chooser
          * activity.
          */
-        public fun setComplicationConfigExtras(extras: Bundle?): Builder {
-            this.complicationConfigExtras = extras
+        public fun setConfigExtras(extras: Bundle): Builder {
+            this.configExtras = extras
             return this
         }
 
@@ -418,19 +406,19 @@
         public fun build(): Complication = Complication(
             id,
             boundsType,
-            complicationBounds,
+            bounds,
             renderer,
             supportedTypes,
             defaultProviderPolicy,
             defaultProviderType,
             initiallyEnabled,
-            complicationConfigExtras,
+            configExtras,
             fixedComplicationProvider
         )
     }
 
     init {
-        canvasComplication.onAttach(this)
+        renderer.onAttach(this)
     }
 
     internal interface InvalidateListener {
@@ -450,11 +438,11 @@
      * Note it's not allowed to change the bounds of a background complication because
      * they are assumed to always cover the entire screen.
      */
-    public var complicationBounds: ComplicationBounds = complicationBounds
+    public var complicationBounds: ComplicationBounds = bounds
         @UiThread
         get
         @UiThread
-        set(value) {
+        internal set(value) {
             require(boundsType != ComplicationBoundsType.BACKGROUND)
             if (field == value) {
                 return
@@ -489,21 +477,6 @@
             }
         }
 
-    /** The [CanvasComplication] used to render the complication. */
-    public var renderer: CanvasComplication = canvasComplication
-        @UiThread
-        get
-        @UiThread
-        set(value) {
-            if (field == value) {
-                return
-            }
-            renderer.onDetach()
-            value.setIdAndData(renderer.getIdAndData(), true)
-            field = value
-            value.onAttach(this)
-        }
-
     internal var supportedTypesDirty = true
 
     /** The types of complications the complication supports. Must be non-empty. */
@@ -553,7 +526,7 @@
     internal var defaultProviderTypeDirty = true
 
     /**
-     * The default [ComplicationData.ComplicationType] to use alongside [defaultProviderPolicy].
+     * The default [ComplicationType] to use alongside [defaultProviderPolicy].
      */
     public var defaultProviderType: ComplicationType = defaultProviderType
         @UiThread
@@ -612,7 +585,7 @@
         renderParameters: RenderParameters
     ) {
         val bounds = computeBounds(Rect(0, 0, canvas.width, canvas.height))
-        renderer.render(canvas, bounds, calendar, renderParameters)
+        renderer.render(canvas, bounds, calendar, renderParameters, id)
     }
 
     /**
@@ -654,8 +627,8 @@
         // Try the current type if there is one, otherwise fall back to the bounds for the default
         // provider type.
         val unitSquareBounds =
-            renderer.getIdAndData()?.let {
-                complicationBounds.perComplicationTypeBounds[it.complicationData.type]
+            renderer.getData()?.let {
+                complicationBounds.perComplicationTypeBounds[it.type]
             } ?: complicationBounds.perComplicationTypeBounds[defaultProviderType]!!
         unitSquareBounds.intersect(unitSquare)
         return Rect(
@@ -674,9 +647,9 @@
         writer.println("enabled=$enabled")
         writer.println("renderer.isHighlighted=${renderer.isHighlighted}")
         writer.println("boundsType=$boundsType")
-        writer.println("complicationConfigExtras=$complicationConfigExtras")
+        writer.println("configExtras=$configExtras")
         writer.println("supportedTypes=${supportedTypes.joinToString { it.toString() }}")
-        writer.println("complicationConfigExtras=$complicationConfigExtras")
+        writer.println("initiallyEnabled=$initiallyEnabled")
         writer.println(
             "defaultProviderPolicy.primaryProvider=${defaultProviderPolicy.primaryProvider}"
         )
@@ -687,7 +660,7 @@
             "defaultProviderPolicy.systemProviderFallback=" +
                 "${defaultProviderPolicy.systemProviderFallback}"
         )
-        writer.println("data=${renderer.getIdAndData()?.complicationData}")
+        writer.println("data=${renderer.getData()}")
         val bounds = complicationBounds.perComplicationTypeBounds.map {
             "${it.key} -> ${it.value}"
         }
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationOutlineRenderer.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationOutlineRenderer.kt
index befcb84..2467a50 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationOutlineRenderer.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationOutlineRenderer.kt
@@ -44,7 +44,7 @@
 
         /** Draws a thick line around the complication with the given bounds. */
         @JvmStatic
-        public fun drawComplicationSelectOutline(
+        public fun drawComplicationOutline(
             canvas: Canvas,
             bounds: Rect,
             @ColorInt color: Int
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationsManager.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationsManager.kt
index 93c44e30..c0f3c35 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationsManager.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationsManager.kt
@@ -23,15 +23,14 @@
 import android.icu.util.Calendar
 import android.support.wearable.watchface.accessibility.AccessibilityUtils
 import android.support.wearable.watchface.accessibility.ContentDescriptionLabel
+import androidx.annotation.Px
 import androidx.annotation.UiThread
 import androidx.annotation.VisibleForTesting
 import androidx.wear.complications.ComplicationBounds
 import androidx.wear.complications.ComplicationHelperActivity
-import androidx.wear.complications.DefaultComplicationProviderPolicy
 import androidx.wear.complications.data.ComplicationData
 import androidx.wear.complications.data.ComplicationType
 import androidx.wear.complications.data.EmptyComplicationData
-import androidx.wear.complications.data.IdAndComplicationData
 import androidx.wear.watchface.data.ComplicationBoundsType
 import androidx.wear.watchface.style.UserStyle
 import androidx.wear.watchface.style.UserStyleRepository
@@ -65,9 +64,9 @@
         /**
          * Called when the user single taps on a complication.
          *
-         * @param complicationId The watch face's id for the complication single tapped
+         * @param complicationId The watch face's id for the complication that was tapped
          */
-        public fun onComplicationSingleTapped(complicationId: Int) {}
+        public fun onComplicationTapped(complicationId: Int) {}
     }
 
     private lateinit var watchFaceHostApi: WatchFaceHostApi
@@ -81,10 +80,7 @@
 
     private class InitialComplicationConfig(
         val complicationBounds: ComplicationBounds,
-        val enabled: Boolean,
-        val supportedTypes: List<ComplicationType>,
-        val defaultProviderPolicy: DefaultComplicationProviderPolicy,
-        val defaultProviderType: ComplicationType
+        val enabled: Boolean
     )
 
     // Copy of the original complication configs. This is necessary because the semantics of
@@ -96,10 +92,7 @@
             {
                 InitialComplicationConfig(
                     it.complicationBounds,
-                    it.enabled,
-                    it.supportedTypes,
-                    it.defaultProviderPolicy,
-                    it.defaultProviderType
+                    it.enabled
                 )
             }
         )
@@ -199,12 +192,12 @@
                 if (complication.boundsType == ComplicationBoundsType.BACKGROUND) {
                     ComplicationBoundsType.BACKGROUND
                 } else {
-                    complication.renderer.getIdAndData()?.let {
+                    complication.renderer.getData()?.let {
                         labels.add(
                             ContentDescriptionLabel(
                                 watchFaceHostApi.getContext(),
                                 complication.computeBounds(renderer.screenBounds),
-                                it.complicationData.asWireComplicationData()
+                                it.asWireComplicationData()
                             )
                         )
                     }
@@ -278,11 +271,8 @@
     internal fun onComplicationDataUpdate(watchFaceComplicationId: Int, data: ComplicationData) {
         val complication = complications[watchFaceComplicationId] ?: return
         complication.dataDirty = complication.dataDirty ||
-            (complication.renderer.getIdAndData()?.complicationData != data)
-        complication.renderer.setIdAndData(
-            IdAndComplicationData(watchFaceComplicationId, data),
-            true
-        )
+            (complication.renderer.getData() != data)
+        complication.renderer.setData(data, true)
         (complication.complicationData as MutableObservableWatchData<ComplicationData>).value =
             data
     }
@@ -290,7 +280,7 @@
     @UiThread
     internal fun clearComplicationData() {
         for ((_, complication) in complications) {
-            complication.renderer.clearIdAndData()
+            complication.renderer.setData(null, false)
             (complication.complicationData as MutableObservableWatchData).value =
                 EmptyComplicationData()
         }
@@ -328,7 +318,7 @@
      * @param y The y coordinate of the point to perform a hit test
      * @return The complication at coordinates x, y or {@code null} if there isn't one
      */
-    public fun getComplicationAt(x: Int, y: Int): Complication? =
+    public fun getComplicationAt(@Px x: Int, @Px y: Int): Complication? =
         complications.entries.firstOrNull {
             it.value.enabled && it.value.boundsType != ComplicationBoundsType.BACKGROUND &&
                 it.value.computeBounds(renderer.screenBounds).contains(x, y)
@@ -354,8 +344,8 @@
     @UiThread
     internal fun onComplicationSingleTapped(complicationId: Int) {
         // Check if the complication is missing permissions.
-        val data = complications[complicationId]?.renderer?.getIdAndData() ?: return
-        if (data.complicationData.type == ComplicationType.NO_PERMISSION) {
+        val data = complications[complicationId]?.renderer?.getData() ?: return
+        if (data.type == ComplicationType.NO_PERMISSION) {
             watchFaceHostApi.getContext().startActivity(
                 ComplicationHelperActivity.createPermissionRequestHelperIntent(
                     watchFaceHostApi.getContext(),
@@ -365,9 +355,9 @@
             return
         }
 
-        data.complicationData.tapAction?.send()
+        data.tapAction?.send()
         for (complicationListener in complicationListeners) {
-            complicationListener.onComplicationSingleTapped(complicationId)
+            complicationListener.onComplicationTapped(complicationId)
         }
     }
 
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/GlesTextureComplication.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/GlesTextureComplication.kt
index 4caca47..a71b06a 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/GlesTextureComplication.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/GlesTextureComplication.kt
@@ -42,7 +42,10 @@
     textureHeight: Int,
 
     /** The texture type, e.g. GLES20.GL_TEXTURE_2D */
-    private val textureType: Int
+    private val textureType: Int,
+
+    /** The id of the associated [Complication]. */
+    public val id: Int
 ) {
     private val texture = createTexture(textureType)
     private val bitmap = Bitmap.createBitmap(
@@ -56,7 +59,7 @@
     /** Renders [canvasComplication] to an OpenGL texture. */
     public fun renderToTexture(calendar: Calendar, renderParameters: RenderParameters) {
         canvas.drawColor(Color.BLACK)
-        canvasComplication.render(canvas, bounds, calendar, renderParameters)
+        canvasComplication.render(canvas, bounds, calendar, renderParameters, id)
         bind()
         GLUtils.texImage2D(textureType, 0, bitmap, 0)
     }
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/RenderParameters.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/RenderParameters.kt
index 563109b..8d9b8ad 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/RenderParameters.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/RenderParameters.kt
@@ -87,6 +87,7 @@
 
     /** Specifies the tint should be used with [LayerMode.DRAW_OUTLINED] .*/
     @ColorInt
+    @get:ColorInt
     public val outlineTint: Int
 ) {
     /**
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Renderer.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Renderer.kt
index a1df4f19..558ff6a 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Renderer.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Renderer.kt
@@ -163,9 +163,6 @@
             }
         }
 
-    /** Allows the renderer to finalize init after the child class's constructor has finished. */
-    internal open fun onPostCreate() {}
-
     /** Called when the Renderer is destroyed. */
     @UiThread
     public open fun onDestroy() {
@@ -290,7 +287,7 @@
          * animation once per second it can adjust the frame rate inorder to sleep when not
          * animating.
          */
-        @IntRange(from = 0, to = 10000)
+        @IntRange(from = 0, to = 60000)
         interactiveDrawModeUpdateDelayMillis: Long
     ) : Renderer(
         surfaceHolder,
@@ -371,6 +368,7 @@
 
     /**
      * Watch faces that require [GLES20] rendering should extend their [Renderer] from this class.
+     * Before passing to the [WatchFace] constructor [initOpenGlContext] must be called.
      */
     public abstract class GlesRenderer @JvmOverloads constructor(
         /** The [SurfaceHolder] whose [android.view.Surface] [render] will draw into. */
@@ -390,7 +388,7 @@
          * animation once per second it can adjust the frame rate inorder to sleep when not
          * animating.
          */
-        @IntRange(from = 0, to = 10000)
+        @IntRange(from = 0, to = 60000)
         interactiveDrawModeUpdateDelayMillis: Long,
 
         /**
@@ -413,7 +411,8 @@
             private const val TAG = "Gles2WatchFace"
         }
 
-        private var eglDisplay: EGLDisplay? = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY).apply {
+        /** The GlesRenderer's [EGLDisplay]. */
+        public var eglDisplay: EGLDisplay? = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY).apply {
             if (this == EGL14.EGL_NO_DISPLAY) {
                 throw RuntimeException("eglGetDisplay returned EGL_NO_DISPLAY")
             }
@@ -424,10 +423,12 @@
             }
         }
 
-        private var eglConfig: EGLConfig = chooseEglConfig(eglDisplay!!)
+        /** The GlesRenderer's [EGLConfig]. */
+        public var eglConfig: EGLConfig = chooseEglConfig(eglDisplay!!)
 
+        /** The GlesRenderer's [EGLContext]. */
         @SuppressWarnings("SyntheticAccessor")
-        private var eglContext: EGLContext? = EGL14.eglCreateContext(
+        public var eglContext: EGLContext? = EGL14.eglCreateContext(
             eglDisplay,
             eglConfig,
             EGL14.EGL_NO_CONTEXT,
@@ -443,6 +444,7 @@
 
         private var eglSurface: EGLSurface? = null
         private var calledOnGlContextCreated = false
+        internal var initDone = false
 
         /**
          * Chooses the EGLConfig to use.
@@ -538,7 +540,12 @@
             }
         }
 
-        internal override fun onPostCreate() {
+        /**
+         * Initializes the GlesRenderer, and calls [onGlSurfaceCreated]. It is an error to construct
+         * a [WatchFace] before this method has been called.
+         */
+        @UiThread
+        public fun initOpenGlContext() {
             surfaceHolder.addCallback(object : SurfaceHolder.Callback {
                 @SuppressLint("SyntheticAccessor")
                 override fun surfaceChanged(
@@ -562,10 +569,14 @@
                 }
             })
 
+            // Note we have to call this after the derived class's init() method has run or it's
+            // typically going to fail because members have not been initialized.
             createWindowSurface(
                 surfaceHolder.surfaceFrame.width(),
                 surfaceHolder.surfaceFrame.height()
             )
+
+            initDone = true
         }
 
         /** Called when a new GL context is created. It's safe to use GL APIs in this method. */
@@ -580,7 +591,7 @@
          * @param height height of surface in pixels
          */
         @UiThread
-        public open fun onGlSurfaceCreated(width: Int, height: Int) {
+        public open fun onGlSurfaceCreated(@Px width: Int, @Px height: Int) {
         }
 
         internal override fun renderInternal(
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
index 67c80de..a3bbab5 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
@@ -44,8 +44,6 @@
 import androidx.wear.utility.TraceEvent
 import androidx.wear.complications.SystemProviders
 import androidx.wear.complications.data.ComplicationData
-import androidx.wear.complications.data.IdAndComplicationData
-import androidx.wear.complications.data.NoDataComplicationData
 import androidx.wear.watchface.control.IInteractiveWatchFaceSysUI
 import androidx.wear.watchface.style.UserStyle
 import androidx.wear.watchface.style.UserStyleRepository
@@ -136,6 +134,14 @@
 ) {
     internal var tapListener: TapListener? = null
 
+    init {
+        if (renderer is Renderer.GlesRenderer) {
+            require(renderer.initDone) {
+                "Did you forget to call GlesRenderer.initOpenGLContext?"
+            }
+        }
+    }
+
     public companion object {
         /** Returns whether [LegacyWatchFaceOverlayStyle] is supported on this device. */
         @JvmStatic
@@ -666,19 +672,14 @@
             idToComplicationData: Map<Int, ComplicationData>?
         ): Bitmap = TraceEvent("WFEditorDelegate.takeScreenshot").use {
             val oldComplicationData =
-                complicationsManager.complications.values.map {
-                    it.renderer.getIdAndData() ?: IdAndComplicationData(
-                        it.id,
-                        NoDataComplicationData()
-                    )
-                }
+                complicationsManager.complications.values.associateBy(
+                    { it.id },
+                    { it.renderer.getData() }
+                )
 
             idToComplicationData?.let {
                 for ((id, complicationData) in it) {
-                    complicationsManager[id]!!.renderer.setIdAndData(
-                        IdAndComplicationData(id, complicationData),
-                        false
-                    )
+                    complicationsManager[id]!!.renderer.setData(complicationData, false)
                 }
             }
             val screenShot = renderer.takeScreenshot(
@@ -688,9 +689,8 @@
                 renderParameters
             )
             if (idToComplicationData != null) {
-                for (idAndData in oldComplicationData) {
-                    complicationsManager[idAndData.complicationId]!!.renderer
-                        .setIdAndData(idAndData, false)
+                for ((id, data) in oldComplicationData) {
+                    complicationsManager[id]!!.renderer.setData(data, false)
                 }
             }
             return screenShot
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index 978e628..3cf69f9 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -49,8 +49,6 @@
 import androidx.wear.complications.SystemProviders.ProviderId
 import androidx.wear.complications.data.ComplicationData
 import androidx.wear.complications.data.ComplicationType
-import androidx.wear.complications.data.IdAndComplicationData
-import androidx.wear.complications.data.NoDataComplicationData
 import androidx.wear.complications.data.asApiComplicationData
 import androidx.wear.utility.AsyncTraceEvent
 import androidx.wear.utility.TraceEvent
@@ -352,6 +350,7 @@
         private var pendingVisibilityChanged: Boolean? = null
         private var pendingComplicationDataUpdates = ArrayList<PendingComplicationData>()
         private var complicationsActivated = false
+        private var watchFaceInitStarted = false
 
         // Only valid after onSetBinder has been called.
         private var systemApiVersion = -1
@@ -363,7 +362,7 @@
         internal var lastActiveComplications: IntArray? = null
         internal var lastA11yLabels: Array<ContentDescriptionLabel>? = null
 
-        private var watchFaceInitStarted = false
+        private var firstOnSurfaceChangedReceived = false
         private var asyncWatchFaceConstructionPending = false
 
         private var initialUserStyle: UserStyleWireFormat? = null
@@ -371,17 +370,14 @@
 
         private var createdBy = "?"
 
-        init {
-            TraceEvent("EngineWrapper.init").use {
-                // If this is a headless instance then we don't want to create a WCS instance.
-                if (!mutableWatchState.isHeadless) {
-                    maybeCreateWCSApi()
-                }
-            }
-        }
-
+        /** Note this function should only be called once. */
         @SuppressWarnings("NewApi")
-        private fun maybeCreateWCSApi() {
+        private fun maybeCreateWCSApi(): Unit = TraceEvent("EngineWrapper.maybeCreateWCSApi").use {
+            // If this is a headless instance then we don't want to create a WCS instance.
+            if (mutableWatchState.isHeadless) {
+                return
+            }
+
             val pendingWallpaperInstance =
                 InteractiveInstanceManager.takePendingWallpaperInteractiveWatchFaceInstance()
 
@@ -546,10 +542,10 @@
                             it.value.defaultProviderType.asWireComplicationType(),
                             it.value.enabled,
                             it.value.initiallyEnabled,
-                            it.value.renderer.getIdAndData()?.complicationData?.type
-                                ?.asWireComplicationType()
+                            it.value.renderer.getData()?.type?.asWireComplicationType()
                                 ?: ComplicationType.NO_DATA.asWireComplicationType(),
-                            it.value.fixedComplicationProvider
+                            it.value.fixedComplicationProvider,
+                            it.value.configExtras
                         )
                     )
                 }
@@ -609,21 +605,15 @@
             }
 
             val oldComplicationData =
-                watchFaceImpl.complicationsManager.complications.values.map {
-                    it.renderer.getIdAndData() ?: IdAndComplicationData(
-                        it.id,
-                        NoDataComplicationData()
-                    )
-                }
+                watchFaceImpl.complicationsManager.complications.values.associateBy(
+                    { it.id },
+                    { it.renderer.getData() }
+                )
+
             params.idAndComplicationDatumWireFormats?.let {
                 for (idAndData in it) {
                     watchFaceImpl.complicationsManager[idAndData.id]!!.renderer
-                        .setIdAndData(
-                            IdAndComplicationData(
-                                idAndData.id, idAndData.complicationData.asApiComplicationData()
-                            ),
-                            false
-                        )
+                        .setData(idAndData.complicationData.asApiComplicationData(), false)
                 }
             }
 
@@ -640,9 +630,8 @@
             }
 
             if (params.idAndComplicationDatumWireFormats != null) {
-                for (idAndData in oldComplicationData) {
-                    watchFaceImpl.complicationsManager[idAndData.complicationId]!!.renderer
-                        .setIdAndData(idAndData, false)
+                for ((id, data) in oldComplicationData) {
+                    watchFaceImpl.complicationsManager[id]!!.renderer.setData(data, false)
                 }
             }
 
@@ -673,15 +662,12 @@
                         Bitmap.Config.ARGB_8888
                     )
 
-                var prevIdAndComplicationData: IdAndComplicationData? = null
+                var prevData: ComplicationData? = null
                 val screenshotComplicationData = params.complicationData
                 if (screenshotComplicationData != null) {
-                    prevIdAndComplicationData = it.renderer.getIdAndData()
-                    it.renderer.setIdAndData(
-                        IdAndComplicationData(
-                            params.complicationId,
-                            screenshotComplicationData
-                        ),
+                    prevData = it.renderer.getData()
+                    it.renderer.setData(
+                        screenshotComplicationData.asApiComplicationData(),
                         false
                     )
                 }
@@ -690,12 +676,13 @@
                     Canvas(complicationBitmap),
                     Rect(0, 0, bounds.width(), bounds.height()),
                     calendar,
-                    RenderParameters(params.renderParametersWireFormat)
+                    RenderParameters(params.renderParametersWireFormat),
+                    params.complicationId
                 )
 
                 // Restore previous ComplicationData & style if required.
                 if (params.complicationData != null) {
-                    it.renderer.setIdAndData(prevIdAndComplicationData, false)
+                    it.renderer.setData(prevData, false)
                 }
 
                 if (newStyle != null) {
@@ -754,6 +741,23 @@
             )
         }
 
+        override fun onSurfaceChanged(
+            holder: SurfaceHolder?,
+            format: Int,
+            width: Int,
+            height: Int
+        ): Unit = TraceEvent("EngineWrapper.onSurfaceChanged").use {
+            super.onSurfaceChanged(holder, format, width, height)
+
+            // We can only call maybeCreateWCSApi once. For OpenGL watch faces we need to wait for
+            // onSurfaceChanged before bootstrapping because the surface isn't valid for creating
+            // an EGL context until then.
+            if (!firstOnSurfaceChangedReceived) {
+                maybeCreateWCSApi()
+                firstOnSurfaceChangedReceived = true
+            }
+        }
+
         override fun onDestroy(): Unit = TraceEvent("EngineWrapper.onDestroy").use {
             destroyed = true
             uiThreadHandler.removeCallbacks(invalidateRunnable)
@@ -951,8 +955,6 @@
 
             mutableWatchState.isVisible.value = true
             mutableWatchState.isAmbient.value = false
-
-            watchFaceImpl.renderer.onPostCreate()
             return HeadlessWatchFaceImpl(this, uiThreadHandler)
         }
 
@@ -985,7 +987,6 @@
 
             params.idAndComplicationDataWireFormats?.let { setComplicationDataList(it) }
 
-            watchFaceImpl.renderer.onPostCreate()
             val visibility = pendingVisibilityChanged
             if (visibility != null) {
                 onVisibilityChanged(visibility)
@@ -1053,7 +1054,6 @@
 
                 val watchState = mutableWatchState.asWatchState()
                 createWatchFaceInternal(watchState, surfaceHolder, "maybeCreateWatchFace")
-                watchFaceImpl.renderer.onPostCreate()
 
                 val backgroundAction = pendingBackgroundAction
                 if (backgroundAction != null) {
@@ -1292,6 +1292,7 @@
                 }
             }
             writer.println("createdBy=$createdBy")
+            writer.println("firstOnSurfaceChanged=$firstOnSurfaceChangedReceived")
             writer.println("watchFaceInitStarted=$watchFaceInitStarted")
             writer.println("asyncWatchFaceConstructionPending=$asyncWatchFaceConstructionPending")
             writer.println("ignoreNextOnVisibilityChanged=$ignoreNextOnVisibilityChanged")
diff --git a/wear/wear-watchface/src/test/java/androidx/wear/watchface/AsyncWatchFaceInitTest.kt b/wear/wear-watchface/src/test/java/androidx/wear/watchface/AsyncWatchFaceInitTest.kt
index 718dd4c..719ede7 100644
--- a/wear/wear-watchface/src/test/java/androidx/wear/watchface/AsyncWatchFaceInitTest.kt
+++ b/wear/wear-watchface/src/test/java/androidx/wear/watchface/AsyncWatchFaceInitTest.kt
@@ -91,6 +91,7 @@
 @RunWith(WatchFaceTestRunner::class)
 public class AsyncWatchFaceInitTest {
     private val handler = mock<Handler>()
+    private val surfaceHolder = mock<SurfaceHolder>()
     private var looperTimeMillis = 0L
     private val pendingTasks = PriorityQueue<Task>()
     private val userStyleRepository = UserStyleRepository(UserStyleSchema(emptyList()))
@@ -176,6 +177,7 @@
         )
 
         val engineWrapper = service.onCreateEngine() as WatchFaceService.EngineWrapper
+        engineWrapper.onSurfaceChanged(surfaceHolder, 0, 100, 100)
 
         runPostedTasksFor(0)
 
@@ -215,7 +217,8 @@
             initParams
         )
 
-        service.onCreateEngine() as WatchFaceService.EngineWrapper
+        val engineWrapper = service.onCreateEngine() as WatchFaceService.EngineWrapper
+        engineWrapper.onSurfaceChanged(surfaceHolder, 0, 100, 100)
         runPostedTasksFor(0)
 
         var pendingInteractiveWatchFaceWcs: IInteractiveWatchFaceWCS? = null
diff --git a/wear/wear-watchface/src/test/java/androidx/wear/watchface/TestCommon.kt b/wear/wear-watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
index a69d6e0..5796be5 100644
--- a/wear/wear-watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
+++ b/wear/wear-watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
@@ -31,7 +31,7 @@
 import android.support.wearable.watchface.accessibility.ContentDescriptionLabel
 import android.view.SurfaceHolder
 import androidx.test.core.app.ApplicationProvider
-import androidx.wear.complications.data.IdAndComplicationData
+import androidx.wear.complications.data.asApiComplicationData
 import androidx.wear.watchface.control.data.WallpaperInteractiveWatchFaceInstanceParams
 import androidx.wear.watchface.style.UserStyle
 import androidx.wear.watchface.style.UserStyleRepository
@@ -67,7 +67,7 @@
 
         complicationsManager.addTapListener(
             object : ComplicationsManager.TapCallback {
-                override fun onComplicationSingleTapped(complicationId: Int) {
+                override fun onComplicationTapped(complicationId: Int) {
                     complicationSingleTapped = complicationId
                     singleTapCount++
                 }
@@ -206,18 +206,15 @@
     }
 }
 
-fun createIdAndComplicationData(id: Int) =
-    IdAndComplicationData(
-        id,
-        ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
-            .setShortText(ComplicationText.plainText("Test Text"))
-            .setTapAction(
-                PendingIntent.getActivity(
-                    ApplicationProvider.getApplicationContext(), 0,
-                    Intent("Fake intent"), 0
-                )
-            ).build()
-    )
+fun createComplicationData() =
+    ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
+        .setShortText(ComplicationText.plainText("Test Text"))
+        .setTapAction(
+            PendingIntent.getActivity(
+                ApplicationProvider.getApplicationContext(), 0,
+                Intent("Fake intent"), 0
+            )
+        ).build().asApiComplicationData()
 
 /**
  * We need to prevent roboloetric from instrumenting our classes or things break...
diff --git a/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt b/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
index c7457c6..abd55ea 100644
--- a/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
+++ b/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
@@ -165,7 +165,7 @@
                 complicationDrawableLeft,
                 watchState.asWatchState()
             ).apply {
-                setIdAndData(createIdAndComplicationData(LEFT_COMPLICATION_ID), false)
+                setData(createComplicationData(), false)
             },
             listOf(
                 ComplicationType.RANGED_VALUE,
@@ -186,7 +186,7 @@
                 complicationDrawableRight,
                 watchState.asWatchState()
             ).apply {
-                setIdAndData(createIdAndComplicationData(RIGHT_COMPLICATION_ID), false)
+                setData(createComplicationData(), false)
             },
             listOf(
                 ComplicationType.RANGED_VALUE,
@@ -207,7 +207,7 @@
                 complicationDrawableBackground,
                 watchState.asWatchState()
             ).apply {
-                setIdAndData(createIdAndComplicationData(BACKGROUND_COMPLICATION_ID), false)
+                setData(createComplicationData(), false)
             },
             listOf(
                 ComplicationType.PHOTO_IMAGE
@@ -2279,7 +2279,7 @@
             )
         )
 
-        service.onCreateEngine()
+        service.onCreateEngine().onSurfaceChanged(surfaceHolder, 0, 100, 100)
 
         runPendingPostedDispatchedContinuationTasks()
 
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/DocumentStartJavaScriptActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/DocumentStartJavaScriptActivity.java
index adc43f9..df27a0e 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/DocumentStartJavaScriptActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/DocumentStartJavaScriptActivity.java
@@ -44,8 +44,6 @@
  * An {@link Activity} to exercise {@link WebViewCompat#addDocumentStartJavaScript(WebView, String,
  * Set)} related functionality.
  */
-// TODO(ctzsm): Remove the @SuppressLint after addDocumentStartJavaScript is unhidden.
-@SuppressLint("RestrictedApi")
 public class DocumentStartJavaScriptActivity extends AppCompatActivity {
     private final Uri mExampleUri = new Uri.Builder()
                                             .scheme("https")
diff --git a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ProxyOverrideActivity.java b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ProxyOverrideActivity.java
index b8a1b92..5d58caf 100644
--- a/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ProxyOverrideActivity.java
+++ b/webkit/integration-tests/testapp/src/main/java/com/example/androidx/webkit/ProxyOverrideActivity.java
@@ -16,7 +16,6 @@
 
 package com.example.androidx.webkit;
 
-import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.os.Bundle;
 import android.view.View;
@@ -35,8 +34,6 @@
 /**
  * An {@link Activity} to exercise Proxy Override functionality.
  */
-//TODO(laisminchillo) remove when Reverse Bypass is launched
-@SuppressLint("RestrictedApi")
 public class ProxyOverrideActivity extends AppCompatActivity {
     private Proxy mProxy;
     private Button mSetProxyOverrideButton;
@@ -100,7 +97,7 @@
                 .addBypassRule("www.anotherbypassurl.com")
                 // Set reverse bypass if the checkbox was checked. With reverse bypass, only
                 // the URLs in the bypass list will use the proxy settings.
-                .setReverseBypass(mReverseBypassCheckBox.isChecked())
+                .setReverseBypassEnabled(mReverseBypassCheckBox.isChecked())
                 .build();
 
         // Call setProxyOverride and specify a callback
diff --git a/webkit/webkit/api/current.txt b/webkit/webkit/api/current.txt
index 15fe6d7..076ad49 100644
--- a/webkit/webkit/api/current.txt
+++ b/webkit/webkit/api/current.txt
@@ -8,6 +8,7 @@
   public final class ProxyConfig {
     method public java.util.List<java.lang.String!> getBypassRules();
     method public java.util.List<androidx.webkit.ProxyConfig.ProxyRule!> getProxyRules();
+    method public boolean isReverseBypassEnabled();
     field public static final String MATCH_ALL_SCHEMES = "*";
     field public static final String MATCH_HTTP = "http";
     field public static final String MATCH_HTTPS = "https";
@@ -24,6 +25,7 @@
     method public androidx.webkit.ProxyConfig build();
     method public androidx.webkit.ProxyConfig.Builder bypassSimpleHostnames();
     method public androidx.webkit.ProxyConfig.Builder removeImplicitRules();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.PROXY_OVERRIDE_REVERSE_BYPASS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public androidx.webkit.ProxyConfig.Builder setReverseBypassEnabled(boolean);
   }
 
   public static final class ProxyConfig.ProxyRule {
@@ -43,6 +45,10 @@
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void showInterstitial(boolean);
   }
 
+  public abstract class ScriptHandler {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DOCUMENT_START_SCRIPT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void remove();
+  }
+
   public abstract class ServiceWorkerClientCompat {
     ctor public ServiceWorkerClientCompat();
     method @WorkerThread public abstract android.webkit.WebResourceResponse? shouldInterceptRequest(android.webkit.WebResourceRequest);
@@ -185,6 +191,7 @@
   }
 
   public class WebViewCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DOCUMENT_START_SCRIPT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ScriptHandler addDocumentStartJavaScript(android.webkit.WebView, String, java.util.Set<java.lang.String!>);
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void addWebMessageListener(android.webkit.WebView, String, java.util.Set<java.lang.String!>, androidx.webkit.WebViewCompat.WebMessageListener);
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebMessagePortCompat![] createWebMessageChannel(android.webkit.WebView);
     method public static android.content.pm.PackageInfo? getCurrentWebViewPackage(android.content.Context);
@@ -216,6 +223,7 @@
     method public static boolean isFeatureSupported(String);
     field public static final String CREATE_WEB_MESSAGE_CHANNEL = "CREATE_WEB_MESSAGE_CHANNEL";
     field public static final String DISABLED_ACTION_MODE_MENU_ITEMS = "DISABLED_ACTION_MODE_MENU_ITEMS";
+    field public static final String DOCUMENT_START_SCRIPT = "DOCUMENT_START_SCRIPT";
     field public static final String FORCE_DARK = "FORCE_DARK";
     field public static final String FORCE_DARK_STRATEGY = "FORCE_DARK_STRATEGY";
     field public static final String GET_WEB_CHROME_CLIENT = "GET_WEB_CHROME_CLIENT";
@@ -225,6 +233,7 @@
     field public static final String OFF_SCREEN_PRERASTER = "OFF_SCREEN_PRERASTER";
     field public static final String POST_WEB_MESSAGE = "POST_WEB_MESSAGE";
     field public static final String PROXY_OVERRIDE = "PROXY_OVERRIDE";
+    field public static final String PROXY_OVERRIDE_REVERSE_BYPASS = "PROXY_OVERRIDE_REVERSE_BYPASS";
     field public static final String RECEIVE_HTTP_ERROR = "RECEIVE_HTTP_ERROR";
     field public static final String RECEIVE_WEB_RESOURCE_ERROR = "RECEIVE_WEB_RESOURCE_ERROR";
     field public static final String SAFE_BROWSING_ALLOWLIST = "SAFE_BROWSING_ALLOWLIST";
diff --git a/webkit/webkit/api/public_plus_experimental_current.txt b/webkit/webkit/api/public_plus_experimental_current.txt
index 15fe6d7..076ad49 100644
--- a/webkit/webkit/api/public_plus_experimental_current.txt
+++ b/webkit/webkit/api/public_plus_experimental_current.txt
@@ -8,6 +8,7 @@
   public final class ProxyConfig {
     method public java.util.List<java.lang.String!> getBypassRules();
     method public java.util.List<androidx.webkit.ProxyConfig.ProxyRule!> getProxyRules();
+    method public boolean isReverseBypassEnabled();
     field public static final String MATCH_ALL_SCHEMES = "*";
     field public static final String MATCH_HTTP = "http";
     field public static final String MATCH_HTTPS = "https";
@@ -24,6 +25,7 @@
     method public androidx.webkit.ProxyConfig build();
     method public androidx.webkit.ProxyConfig.Builder bypassSimpleHostnames();
     method public androidx.webkit.ProxyConfig.Builder removeImplicitRules();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.PROXY_OVERRIDE_REVERSE_BYPASS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public androidx.webkit.ProxyConfig.Builder setReverseBypassEnabled(boolean);
   }
 
   public static final class ProxyConfig.ProxyRule {
@@ -43,6 +45,10 @@
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void showInterstitial(boolean);
   }
 
+  public abstract class ScriptHandler {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DOCUMENT_START_SCRIPT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void remove();
+  }
+
   public abstract class ServiceWorkerClientCompat {
     ctor public ServiceWorkerClientCompat();
     method @WorkerThread public abstract android.webkit.WebResourceResponse? shouldInterceptRequest(android.webkit.WebResourceRequest);
@@ -185,6 +191,7 @@
   }
 
   public class WebViewCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DOCUMENT_START_SCRIPT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ScriptHandler addDocumentStartJavaScript(android.webkit.WebView, String, java.util.Set<java.lang.String!>);
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void addWebMessageListener(android.webkit.WebView, String, java.util.Set<java.lang.String!>, androidx.webkit.WebViewCompat.WebMessageListener);
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebMessagePortCompat![] createWebMessageChannel(android.webkit.WebView);
     method public static android.content.pm.PackageInfo? getCurrentWebViewPackage(android.content.Context);
@@ -216,6 +223,7 @@
     method public static boolean isFeatureSupported(String);
     field public static final String CREATE_WEB_MESSAGE_CHANNEL = "CREATE_WEB_MESSAGE_CHANNEL";
     field public static final String DISABLED_ACTION_MODE_MENU_ITEMS = "DISABLED_ACTION_MODE_MENU_ITEMS";
+    field public static final String DOCUMENT_START_SCRIPT = "DOCUMENT_START_SCRIPT";
     field public static final String FORCE_DARK = "FORCE_DARK";
     field public static final String FORCE_DARK_STRATEGY = "FORCE_DARK_STRATEGY";
     field public static final String GET_WEB_CHROME_CLIENT = "GET_WEB_CHROME_CLIENT";
@@ -225,6 +233,7 @@
     field public static final String OFF_SCREEN_PRERASTER = "OFF_SCREEN_PRERASTER";
     field public static final String POST_WEB_MESSAGE = "POST_WEB_MESSAGE";
     field public static final String PROXY_OVERRIDE = "PROXY_OVERRIDE";
+    field public static final String PROXY_OVERRIDE_REVERSE_BYPASS = "PROXY_OVERRIDE_REVERSE_BYPASS";
     field public static final String RECEIVE_HTTP_ERROR = "RECEIVE_HTTP_ERROR";
     field public static final String RECEIVE_WEB_RESOURCE_ERROR = "RECEIVE_WEB_RESOURCE_ERROR";
     field public static final String SAFE_BROWSING_ALLOWLIST = "SAFE_BROWSING_ALLOWLIST";
diff --git a/webkit/webkit/api/restricted_current.txt b/webkit/webkit/api/restricted_current.txt
index 15fe6d7..076ad49 100644
--- a/webkit/webkit/api/restricted_current.txt
+++ b/webkit/webkit/api/restricted_current.txt
@@ -8,6 +8,7 @@
   public final class ProxyConfig {
     method public java.util.List<java.lang.String!> getBypassRules();
     method public java.util.List<androidx.webkit.ProxyConfig.ProxyRule!> getProxyRules();
+    method public boolean isReverseBypassEnabled();
     field public static final String MATCH_ALL_SCHEMES = "*";
     field public static final String MATCH_HTTP = "http";
     field public static final String MATCH_HTTPS = "https";
@@ -24,6 +25,7 @@
     method public androidx.webkit.ProxyConfig build();
     method public androidx.webkit.ProxyConfig.Builder bypassSimpleHostnames();
     method public androidx.webkit.ProxyConfig.Builder removeImplicitRules();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.PROXY_OVERRIDE_REVERSE_BYPASS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public androidx.webkit.ProxyConfig.Builder setReverseBypassEnabled(boolean);
   }
 
   public static final class ProxyConfig.ProxyRule {
@@ -43,6 +45,10 @@
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void showInterstitial(boolean);
   }
 
+  public abstract class ScriptHandler {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DOCUMENT_START_SCRIPT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void remove();
+  }
+
   public abstract class ServiceWorkerClientCompat {
     ctor public ServiceWorkerClientCompat();
     method @WorkerThread public abstract android.webkit.WebResourceResponse? shouldInterceptRequest(android.webkit.WebResourceRequest);
@@ -185,6 +191,7 @@
   }
 
   public class WebViewCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DOCUMENT_START_SCRIPT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ScriptHandler addDocumentStartJavaScript(android.webkit.WebView, String, java.util.Set<java.lang.String!>);
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void addWebMessageListener(android.webkit.WebView, String, java.util.Set<java.lang.String!>, androidx.webkit.WebViewCompat.WebMessageListener);
     method @RequiresFeature(name=androidx.webkit.WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebMessagePortCompat![] createWebMessageChannel(android.webkit.WebView);
     method public static android.content.pm.PackageInfo? getCurrentWebViewPackage(android.content.Context);
@@ -216,6 +223,7 @@
     method public static boolean isFeatureSupported(String);
     field public static final String CREATE_WEB_MESSAGE_CHANNEL = "CREATE_WEB_MESSAGE_CHANNEL";
     field public static final String DISABLED_ACTION_MODE_MENU_ITEMS = "DISABLED_ACTION_MODE_MENU_ITEMS";
+    field public static final String DOCUMENT_START_SCRIPT = "DOCUMENT_START_SCRIPT";
     field public static final String FORCE_DARK = "FORCE_DARK";
     field public static final String FORCE_DARK_STRATEGY = "FORCE_DARK_STRATEGY";
     field public static final String GET_WEB_CHROME_CLIENT = "GET_WEB_CHROME_CLIENT";
@@ -225,6 +233,7 @@
     field public static final String OFF_SCREEN_PRERASTER = "OFF_SCREEN_PRERASTER";
     field public static final String POST_WEB_MESSAGE = "POST_WEB_MESSAGE";
     field public static final String PROXY_OVERRIDE = "PROXY_OVERRIDE";
+    field public static final String PROXY_OVERRIDE_REVERSE_BYPASS = "PROXY_OVERRIDE_REVERSE_BYPASS";
     field public static final String RECEIVE_HTTP_ERROR = "RECEIVE_HTTP_ERROR";
     field public static final String RECEIVE_WEB_RESOURCE_ERROR = "RECEIVE_WEB_RESOURCE_ERROR";
     field public static final String SAFE_BROWSING_ALLOWLIST = "SAFE_BROWSING_ALLOWLIST";
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/ProxyControllerTest.java b/webkit/webkit/src/androidTest/java/androidx/webkit/ProxyControllerTest.java
index 919b0d3..8a5ff6d 100644
--- a/webkit/webkit/src/androidTest/java/androidx/webkit/ProxyControllerTest.java
+++ b/webkit/webkit/src/androidTest/java/androidx/webkit/ProxyControllerTest.java
@@ -161,7 +161,7 @@
         setProxyOverrideSync(new ProxyConfig.Builder()
                 .addProxyRule(mProxyServer.getHostName() + ":" + mProxyServer.getPort())
                 .addBypassRule(bypassUrl)
-                .setReverseBypass(true)
+                .setReverseBypassEnabled(true)
                 .build());
         mWebViewOnUiThread.loadUrl(contentUrl);
 
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewDocumentStartJavaScriptTest.java b/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewDocumentStartJavaScriptTest.java
index f5bc99e..96a0e84 100644
--- a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewDocumentStartJavaScriptTest.java
+++ b/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewDocumentStartJavaScriptTest.java
@@ -114,14 +114,14 @@
     @Test
     public void testAddDocumentStartJavaScriptRemoveScript() throws Exception {
         mWebViewOnUiThread.addWebMessageListener(JS_OBJECT_NAME, MATCH_EXAMPLE_COM, mListener);
-        ScriptReferenceCompat scriptReference =
+        ScriptHandler scriptHandler =
                 mWebViewOnUiThread.addDocumentStartJavaScript(BASIC_SCRIPT, MATCH_EXAMPLE_COM);
 
         loadHtmlSync(BASIC_USAGE);
         TestWebMessageListener.Data data = mListener.waitForOnPostMessage();
         Assert.assertEquals("hello", data.mMessage.getData());
 
-        WebkitUtils.onMainThread(scriptReference::remove);
+        WebkitUtils.onMainThread(scriptHandler::remove);
         loadHtmlSync(BASIC_USAGE);
 
         Assert.assertTrue("No more message at this point.", mListener.hasNoMoreOnPostMessage());
diff --git a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java b/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
index 6e5d688..3ba2829 100644
--- a/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
+++ b/webkit/webkit/src/androidTest/java/androidx/webkit/WebViewOnUiThread.java
@@ -236,7 +236,7 @@
                 () -> WebViewCompat.removeWebMessageListener(mWebView, jsObjectName));
     }
 
-    public ScriptReferenceCompat addDocumentStartJavaScript(
+    public ScriptHandler addDocumentStartJavaScript(
             String script, Set<String> allowedOriginRules) {
         return WebkitUtils.onMainThreadSync(() -> WebViewCompat.addDocumentStartJavaScript(
                 mWebView, script, allowedOriginRules));
diff --git a/webkit/webkit/src/main/java/androidx/webkit/ProxyConfig.java b/webkit/webkit/src/main/java/androidx/webkit/ProxyConfig.java
index 3fc5e78..d8ea0c2 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/ProxyConfig.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/ProxyConfig.java
@@ -110,13 +110,15 @@
     }
 
     /**
+     * Returns {@code true} if reverse bypass is enabled. Reverse bypass means that only URLs in the
+     * bypass list will use these proxy settings. {@link #getBypassRules()} returns the URL list.
+     *
+     * <p>See {@link Builder#setReverseBypassEnabled(boolean)} for a more detailed description.
+     *
      * @return reverseBypass
      *
-     * TODO(laisminchillo): unhide this when we're ready to expose this
-     * @hide
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public boolean isReverseBypass() {
+    public boolean isReverseBypassEnabled() {
         return mReverseBypass;
     }
 
@@ -193,7 +195,7 @@
         public Builder(@NonNull ProxyConfig proxyConfig) {
             mProxyRules = proxyConfig.getProxyRules();
             mBypassRules = proxyConfig.getBypassRules();
-            mReverseBypass = proxyConfig.isReverseBypass();
+            mReverseBypass = proxyConfig.isReverseBypassEnabled();
         }
 
         /**
@@ -334,23 +336,26 @@
         }
 
         /**
-         * Reverse the bypass list, so only URLs in the bypass list will use these proxy settings.
+         * Reverse the bypass list.
          *
-         * <p>
-         * This method should only be called if
+         * <p>The default value is {@code false}, in which case all URLs will use proxy settings
+         * except the ones in the bypass list, which will be connected to directly instead.
+         *
+         * <p>If set to {@code true}, then only URLs in the bypass list will use these proxy
+         * settings, and all other URLs will be connected to directly.
+         *
+         * <p>Use {@link #addBypassRule(String)} to add bypass rules.
+         *
+         * <p>This method should only be called if
          * {@link WebViewFeature#isFeatureSupported(String)}
          * returns {@code true} for {@link WebViewFeature#PROXY_OVERRIDE_REVERSE_BYPASS}.
          *
          * @return This Builder object
-         *
-         * TODO(laisminchillo): unhide this when we're ready to expose this
-         * @hide
          */
-        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         @RequiresFeature(name = WebViewFeature.PROXY_OVERRIDE_REVERSE_BYPASS,
                 enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
         @NonNull
-        public Builder setReverseBypass(boolean reverseBypass) {
+        public Builder setReverseBypassEnabled(boolean reverseBypass) {
             mReverseBypass = reverseBypass;
             return this;
         }
diff --git a/webkit/webkit/src/main/java/androidx/webkit/ScriptReferenceCompat.java b/webkit/webkit/src/main/java/androidx/webkit/ScriptHandler.java
similarity index 79%
rename from webkit/webkit/src/main/java/androidx/webkit/ScriptReferenceCompat.java
rename to webkit/webkit/src/main/java/androidx/webkit/ScriptHandler.java
index 4f67f42..db7447e 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/ScriptReferenceCompat.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/ScriptHandler.java
@@ -20,14 +20,13 @@
 import androidx.annotation.RestrictTo;
 
 /**
- * TODO(ctzsm): Complete Javadoc
- * @see WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView, String, Set)
+ * This class represents the return result from {@link WebViewCompat#addDocumentStartJavaScript(
+ * android.webkit.WebView, String, Set)}. Call {@link ScriptHandler#remove()} when the
+ * corresponding JavaScript script should be removed.
  *
- * TODO(ctzsm): unhide when ready.
- * @hide
+ * @see WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView, String, Set)
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public abstract class ScriptReferenceCompat {
+public abstract class ScriptHandler {
     /**
      * Removes the corresponding script, it will take effect from next page load.
      */
@@ -40,5 +39,5 @@
      * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
-    public ScriptReferenceCompat() {}
+    public ScriptHandler() {}
 }
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java b/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
index 1c3ad3c..547a9bd 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
@@ -31,7 +31,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresFeature;
-import androidx.annotation.RestrictTo;
 import androidx.annotation.UiThread;
 import androidx.webkit.internal.WebMessagePortImpl;
 import androidx.webkit.internal.WebViewFeatureInternal;
@@ -701,22 +700,49 @@
     }
 
     /**
-     * TODO(ctzsm): Add Javadoc.
-     * TODO(ctzsm): unhide when ready.
-     * @hide
+     * Adds a JavaScript script to the {@link WebView} which will be executed in any frame whose
+     * origin matches {@code allowedOriginRules} when the document begins to load.
+     *
+     * <p>Note that the script will run before any of the page's JavaScript code and the DOM tree
+     * might not be ready at this moment. It will block the loadng of the page until it's finished,
+     * so should be kept as short as possible.
+     *
+     * <p>The injected object from {@link #addWebMessageListener(WebView, String, Set,
+     * WebMessageListener)} API will be injected first and the script can rely on the injected
+     * object to send messages to the app.
+     *
+     * <p>The script will only run in frames which begin loading after the call returns, therefore
+     * it should typically be called before making any {@code loadUrl()}, {@code loadData()} or
+     * {@code loadDataWithBaseURL()} call to load the page.
+     *
+     * <p>This method can be called multiple times to inject multiple scripts. If more than one
+     * script matches a frame's origin, they will be executed in the order they were added.
+     *
+     * <p>See {@link #addWebMessageListener(WebView, String, Set, WebMessageListener)} for the rules
+     * of the {@code allowedOriginRules} parameter.
+     *
+     * <p>This method should only be called if {@link WebViewFeature#isFeatureSupported(String)}
+     * returns true for {@link WebViewFeature#DOCUMENT_START_SCRIPT}.
+     *
+     * @param webview The {@link WebView} instance that we are interacting with.
+     * @param script The JavaScript script to be executed.
+     * @param allowedOriginRules A set of matching rules for the allowed origins.
+     * @return the {@link ScriptHandler}, which is a handle for removing the script.
+     * @throws IllegalArgumentException If one of the {@code allowedOriginRules} is invalid.
+     * @see #addWebMessageListener(WebView, String, Set, WebMessageListener)
+     * @see ScriptHandler
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @RequiresFeature(
             name = WebViewFeature.DOCUMENT_START_SCRIPT,
             enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
-    public static @NonNull ScriptReferenceCompat addDocumentStartJavaScript(
+    public static @NonNull ScriptHandler addDocumentStartJavaScript(
             @NonNull WebView webview,
             @NonNull String script,
             @NonNull Set<String> allowedOriginRules) {
         final WebViewFeatureInternal feature = WebViewFeatureInternal.DOCUMENT_START_SCRIPT;
         if (feature.isSupportedByWebView()) {
-            return getProvider(webview).addDocumentStartJavaScript(
-                    script, allowedOriginRules.toArray(new String[0]));
+            return getProvider(webview)
+                    .addDocumentStartJavaScript(script, allowedOriginRules.toArray(new String[0]));
         } else {
             throw WebViewFeatureInternal.getUnsupportedOperationException();
         }
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java b/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
index ac14d78..73dfdadc 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
@@ -447,21 +447,14 @@
      * Feature for {@link #isFeatureSupported(String)}.
      * This feature covers {@link WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,
      * String, Set)}.
-     *
-     * TODO(ctzsm): unhide when ready.
-     * @hide
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public static final String DOCUMENT_START_SCRIPT = "DOCUMENT_START_SCRIPT";
 
     /**
      * Feature for {@link #isFeatureSupported(String)}.
-     * This feature covers {@link androidx.webkit.ProxyConfig.Builder.setReverseBypass(boolean)}
-     *
-     * TODO(laisminchillo): unhide when ready.
-     * @hide
+     * This feature covers
+     * {@link androidx.webkit.ProxyConfig.Builder#setReverseBypassEnabled(boolean)}
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public static final String PROXY_OVERRIDE_REVERSE_BYPASS = "PROXY_OVERRIDE_REVERSE_BYPASS";
 
     /**
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/ProxyControllerImpl.java b/webkit/webkit/src/main/java/androidx/webkit/internal/ProxyControllerImpl.java
index 5d7174f..ea85232 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/ProxyControllerImpl.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/ProxyControllerImpl.java
@@ -43,7 +43,7 @@
         String[][] proxyRuleArray = proxyRulesToStringArray(proxyConfig.getProxyRules());
         String[] bypassRuleArray = proxyConfig.getBypassRules().toArray(new String[0]);
 
-        if (proxyOverride.isSupportedByWebView() && !proxyConfig.isReverseBypass()) {
+        if (proxyOverride.isSupportedByWebView() && !proxyConfig.isReverseBypassEnabled()) {
             getBoundaryInterface().setProxyOverride(
                     proxyRuleArray, bypassRuleArray, listener, executor);
         } else if (proxyOverride.isSupportedByWebView() && reverseBypass.isSupportedByWebView()) {
@@ -52,7 +52,7 @@
                     bypassRuleArray,
                     listener,
                     executor,
-                    proxyConfig.isReverseBypass());
+                    proxyConfig.isReverseBypassEnabled());
         } else {
             throw WebViewFeatureInternal.getUnsupportedOperationException();
         }
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/ScriptReferenceImpl.java b/webkit/webkit/src/main/java/androidx/webkit/internal/ScriptReferenceImpl.java
index 151f611..328af59 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/ScriptReferenceImpl.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/ScriptReferenceImpl.java
@@ -17,7 +17,7 @@
 package androidx.webkit.internal;
 
 import androidx.annotation.NonNull;
-import androidx.webkit.ScriptReferenceCompat;
+import androidx.webkit.ScriptHandler;
 
 import org.chromium.support_lib_boundary.ScriptReferenceBoundaryInterface;
 import org.chromium.support_lib_boundary.util.BoundaryInterfaceReflectionUtil;
@@ -27,7 +27,7 @@
 /**
  * Internal implementation of {@link androidx.webkit.ScriptReference}.
  */
-public class ScriptReferenceImpl extends ScriptReferenceCompat {
+public class ScriptReferenceImpl extends ScriptHandler {
     private ScriptReferenceBoundaryInterface mBoundaryInterface;
 
     private ScriptReferenceImpl(@NonNull ScriptReferenceBoundaryInterface boundaryInterface) {
@@ -37,7 +37,7 @@
     /**
      * Create an AndroidX ScriptReference from the given InvocationHandler.
      */
-    public static @NonNull ScriptReferenceImpl toScriptReferenceCompat(
+    public static @NonNull ScriptReferenceImpl toScriptHandler(
             @NonNull /* ScriptReference */ InvocationHandler invocationHandler) {
         final ScriptReferenceBoundaryInterface boundaryInterface =
                 BoundaryInterfaceReflectionUtil.castToSuppLibClass(
diff --git a/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewProviderAdapter.java b/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewProviderAdapter.java
index 75676d0..69a2c41 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewProviderAdapter.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/internal/WebViewProviderAdapter.java
@@ -104,7 +104,7 @@
      */
     public @NonNull ScriptReferenceImpl addDocumentStartJavaScript(
             @NonNull String script, @NonNull String[] allowedOriginRules) {
-        return ScriptReferenceImpl.toScriptReferenceCompat(
+        return ScriptReferenceImpl.toScriptHandler(
                 mImpl.addDocumentStartJavaScript(script, allowedOriginRules));
     }
 
diff --git a/work/integration-tests/testapp/src/main/AndroidManifest.xml b/work/integration-tests/testapp/src/main/AndroidManifest.xml
index 2cf1817..8eb67e8 100644
--- a/work/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/work/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -16,7 +16,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     package="androidx.work.integration.testapp">
-
     <application
         android:name=".TestApplication"
         android:allowBackup="true"
@@ -38,17 +37,14 @@
                 <action android:name="android.intent.action.MAIN" />
             </intent-filter>
         </activity>
-
-        <provider
-            android:name="androidx.work.impl.WorkManagerInitializer"
-            android:authorities="${applicationId}.workmanager-init"
-            tools:node="remove" />
-
         <service
             android:name=".RemoteService"
             android:exported="false"
             android:process=":remote" />
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
     </application>
-
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 </manifest>
diff --git a/work/workmanager-lint/src/main/java/androidx/work/lint/RemoveWorkManagerInitializerDetector.kt b/work/workmanager-lint/src/main/java/androidx/work/lint/RemoveWorkManagerInitializerDetector.kt
index c988b43..c8a8c1b 100644
--- a/work/workmanager-lint/src/main/java/androidx/work/lint/RemoveWorkManagerInitializerDetector.kt
+++ b/work/workmanager-lint/src/main/java/androidx/work/lint/RemoveWorkManagerInitializerDetector.kt
@@ -54,7 +54,7 @@
             briefDescription = DESCRIPTION,
             explanation = """
                 If an `android.app.Application` implements `androidx.work.Configuration.Provider`,
-                the default `androidx.work.impl.WorkManagerInitializer` needs to be removed from the
+                the default `androidx.startup.InitializationProvider` needs to be removed from the
                 AndroidManifest.xml file.
             """,
             androidSpecific = true,
@@ -88,10 +88,11 @@
     override fun applicableSuperClasses() = listOf("androidx.work.Configuration.Provider")
 
     override fun visitElement(context: XmlContext, element: Element) {
+        // Check providers
         val providers = element.getElementsByTagName("provider")
         val provider = providers.find { node ->
             val name = node.attributes.getNamedItemNS(ANDROID_URI, ATTR_NAME)?.textContent
-            name == "androidx.work.impl.WorkManagerInitializer"
+            name == "androidx.startup.InitializationProvider"
         }
         if (provider != null) {
             location = context.getLocation(provider)
@@ -100,6 +101,19 @@
                 removedDefaultInitializer = true
             }
         }
+        // Check metadata
+        val metadataElements = element.getElementsByTagName("meta-data")
+        val metadata = metadataElements.find { node ->
+            val name = node.attributes.getNamedItemNS(ANDROID_URI, ATTR_NAME)?.textContent
+            name == "androidx.work.impl.WorkManagerInitializer"
+        }
+        if (metadata != null && !removedDefaultInitializer) {
+            location = context.getLocation(metadata)
+            val remove = metadata.attributes.getNamedItemNS(TOOLS_URI, ATTR_NODE)
+            if (remove?.textContent == "remove") {
+                removedDefaultInitializer = true
+            }
+        }
     }
 
     override fun visitClass(context: JavaContext, declaration: UClass) {
diff --git a/work/workmanager-lint/src/test/java/androidx/work/lint/RemoveWorkManagerInitializerDetectorTest.kt b/work/workmanager-lint/src/test/java/androidx/work/lint/RemoveWorkManagerInitializerDetectorTest.kt
index 98aa770..7a6cb1d 100644
--- a/work/workmanager-lint/src/test/java/androidx/work/lint/RemoveWorkManagerInitializerDetectorTest.kt
+++ b/work/workmanager-lint/src/test/java/androidx/work/lint/RemoveWorkManagerInitializerDetectorTest.kt
@@ -51,7 +51,7 @@
                   package="com.example">
                   <application>
                         <provider
-                          android:name="androidx.work.impl.WorkManagerInitializer"
+                          android:name="androidx.startup.InitializationProvider"
                           android:authorities="com.example.workmanager-init"
                           tools:node="remove"/>
 
@@ -193,9 +193,12 @@
                   package="com.example">
                   <application>
                         <provider
-                          android:name="androidx.work.impl.WorkManagerInitializer"
-                          android:authorities="com.example.workmanager-init"/>
-
+                          android:name="androidx.startup.InitializationProvider"
+                          android:authorities="com.example.workmanager-init">
+                          <meta-data
+                            android:name="androidx.work.impl.WorkManagerInitializer"
+                            android:value="@string/androidx_startup" />
+                      </provider>
                   </application>
                 </manifest>
         """
@@ -213,9 +216,9 @@
             .run()
             .expect(
                 """
-                AndroidManifest.xml:5: Error: Remove androidx.work.impl.WorkManagerInitializer from your AndroidManifest.xml when using on-demand initialization. [RemoveWorkManagerInitializer]
-                         <provider
-                         ^
+                AndroidManifest.xml:8: Error: Remove androidx.work.impl.WorkManagerInitializer from your AndroidManifest.xml when using on-demand initialization. [RemoveWorkManagerInitializer]
+                           <meta-data
+                           ^
                 1 errors, 0 warnings
                 """.trimIndent()
             )
diff --git a/work/workmanager-testing/src/test/java/androidx/work/testing/RobolectricSmokeTest.java b/work/workmanager-testing/src/test/java/androidx/work/testing/RobolectricSmokeTest.java
index db0a013..6f5260a 100644
--- a/work/workmanager-testing/src/test/java/androidx/work/testing/RobolectricSmokeTest.java
+++ b/work/workmanager-testing/src/test/java/androidx/work/testing/RobolectricSmokeTest.java
@@ -51,7 +51,7 @@
         WorkManagerTestInitHelper.initializeTestWorkManager(mContext);
     }
 
-    @Test
+    @Test(timeout = 10000)
     public void testWorker_shouldSucceedSynchronously()
             throws InterruptedException, ExecutionException {
         WorkRequest request = new OneTimeWorkRequest.Builder(TestWorker.class).build();
diff --git a/work/workmanager/build.gradle b/work/workmanager/build.gradle
index 6961031..2ba9ca2 100644
--- a/work/workmanager/build.gradle
+++ b/work/workmanager/build.gradle
@@ -65,6 +65,7 @@
     implementation("androidx.sqlite:sqlite-framework:2.1.0")
     api(GUAVA_LISTENABLE_FUTURE)
     api("androidx.lifecycle:lifecycle-livedata:2.1.0")
+    api("androidx.startup:startup-runtime:1.0.0")
     implementation("androidx.core:core:1.1.0")
     implementation("androidx.lifecycle:lifecycle-service:2.1.0")
     androidTestImplementation(KOTLIN_STDLIB)
diff --git a/work/workmanager/src/main/AndroidManifest.xml b/work/workmanager/src/main/AndroidManifest.xml
index aa94c19..08d71dc 100644
--- a/work/workmanager/src/main/AndroidManifest.xml
+++ b/work/workmanager/src/main/AndroidManifest.xml
@@ -25,12 +25,13 @@
 
     <application>
         <provider
-            android:name="androidx.work.impl.WorkManagerInitializer"
-            android:authorities="${applicationId}.workmanager-init"
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
             android:exported="false"
-            android:multiprocess="true"
-            android:directBootAware="false"
-            tools:targetApi="n"/>
+            tools:node="merge">
+            <meta-data  android:name="androidx.work.impl.WorkManagerInitializer"
+                android:value="androidx.startup" />
+        </provider>
         <service
             android:name="androidx.work.impl.background.systemalarm.SystemAlarmService"
             android:exported="false"
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerInitializer.java b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerInitializer.java
index 7caf8063..39f1b1c3 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerInitializer.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerInitializer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018 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.
@@ -16,65 +16,40 @@
 
 package androidx.work.impl;
 
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
+import android.content.Context;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
+import androidx.startup.Initializer;
 import androidx.work.Configuration;
+import androidx.work.Logger;
 import androidx.work.WorkManager;
 
+import java.util.Collections;
+import java.util.List;
+
 /**
- * The {@link ContentProvider} responsible for initializing {@link WorkManagerImpl}.
+ * Initializes {@link androidx.work.WorkManager} using {@code androidx.startup}.
  *
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class WorkManagerInitializer extends ContentProvider {
+public class WorkManagerInitializer implements Initializer<WorkManager> {
+
+    private static final String TAG = Logger.tagWithPrefix("WrkMgrInitializer");
+
+    @NonNull
     @Override
-    public boolean onCreate() {
+    public WorkManager create(@NonNull Context context) {
         // Initialize WorkManager with the default configuration.
-        WorkManager.initialize(getContext(), new Configuration.Builder().build());
-        return true;
+        Logger.get().debug(TAG, "Initializing WorkManager with default configuration.");
+        WorkManager.initialize(context, new Configuration.Builder().build());
+        return WorkManager.getInstance(context);
     }
 
-    @Nullable
+    @NonNull
     @Override
-    public Cursor query(@NonNull Uri uri,
-            @Nullable String[] projection,
-            @Nullable String selection,
-            @Nullable String[] selectionArgs,
-            @Nullable String sortOrder) {
-        return null;
-    }
-
-    @Nullable
-    @Override
-    public String getType(@NonNull Uri uri) {
-        return null;
-    }
-
-    @Nullable
-    @Override
-    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
-        return null;
-    }
-
-    @Override
-    public int delete(@NonNull Uri uri,
-            @Nullable String selection,
-            @Nullable String[] selectionArgs) {
-        return 0;
-    }
-
-    @Override
-    public int update(@NonNull Uri uri,
-            @Nullable ContentValues values,
-            @Nullable String selection,
-            @Nullable String[] selectionArgs) {
-        return 0;
+    public List<Class<? extends androidx.startup.Initializer<?>>> dependencies() {
+        return Collections.emptyList();
     }
 }