[go: nahoru, domu]

Merge "Migrates Compose libraries to use LibraryType instead of Publish" 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 @@
 ilake@google.com
 jbwoods@google.com
+
+per-file settings.gradle = dustinlam@google.com, rahulrav@google.com
+
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/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/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/appcompat/appcompat/src/androidTest/AndroidManifest.xml b/appcompat/appcompat/src/androidTest/AndroidManifest.xml
index c216f56..63f6f54 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"/>
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_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/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/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/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/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 b40af25..3e48e7c 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlaygroundRootPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlaygroundRootPlugin.kt
@@ -85,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()
             }
         }
@@ -143,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) {
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 45349c3..c68995a 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXUiPlugin.kt
@@ -198,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 6c329d0..1cba767 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -23,8 +23,8 @@
     val ACTIVITY = Version("1.3.0-alpha04")
     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
@@ -48,7 +48,7 @@
     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-rc01")
+    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")
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/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/testConfiguration/AndroidTestXmlBuilder.kt b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilder.kt
index 7ac7283..28372ea 100644
--- a/buildSrc/src/main/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilder.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilder.kt
@@ -59,6 +59,17 @@
             .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))
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/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/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/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/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-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/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/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/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/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/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/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/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 >
+        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 >
         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 >
+        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/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/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,
-             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,
+                     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,
+                 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/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],
                          onOptionSelected(i) }
                     )
                     .padding(horizontal = 16.dp),
                 verticalAlignment = Alignment.CenterVertically
             ) {
-                RadioButton(
-                    selected = (i == selectedOption),
-                     onOptionSelected(i) }
+                Checkbox(
+                    checked = selectedOptions[i],
+                     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/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/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/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/ScrollableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
index fef0896..ab1d15d 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
@@ -874,6 +876,40 @@
     }
 
     @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 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/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
index 2d04b39..4b3d18af 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
@@ -210,7 +210,7 @@
     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) }
             val scope = object : ScrollScope {
@@ -282,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/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 1f6b2da..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
@@ -72,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
@@ -89,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
@@ -103,32 +100,17 @@
             ?: 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 51c4829..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,45 +259,42 @@
     return layout(layoutWidth, layoutHeight) {
         var currentMainAxis = measureResult.itemsScrollOffset
         if (hasSpareSpace) {
-            val items = if (reverseLayout) {
-                measureResult.items.toMutableList().apply { reverse() }
-            } else {
-                measureResult.items
+            val itemsCount = measureResult.items.size
+            val sizes = IntArray(itemsCount) { index ->
+                val reverseLayoutAwareIndex = if (!reverseLayout) index else itemsCount - index - 1
+                measureResult.items[reverseLayoutAwareIndex].size
             }
-            val sizes = IntArray(items.size) { index ->
-                items[index].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/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) {
         this@LazyMeasuredItem.offset = 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/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/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/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/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/internal-lint-checks/src/main/java/androidx/compose/lint/ListIteratorDetector.kt b/compose/internal-lint-checks/src/main/java/androidx/compose/lint/ListIteratorDetector.kt
deleted file mode 100644
index bc8ff03..0000000
--- a/compose/internal-lint-checks/src/main/java/androidx/compose/lint/ListIteratorDetector.kt
+++ /dev/null
@@ -1,241 +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.
- */
-
-@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.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.ClsMethodImpl
-import com.intellij.psi.util.ClassUtil
-import com.intellij.psi.util.InheritanceUtil
-import kotlinx.metadata.KmClassifier
-import kotlinx.metadata.KmDeclarationContainer
-import kotlinx.metadata.KmFunction
-import kotlinx.metadata.jvm.KotlinClassHeader
-import kotlinx.metadata.jvm.KotlinClassMetadata
-import kotlinx.metadata.jvm.signature
-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, JavaListFqn)) {
-                // 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, JavaListFqn)
-            ) {
-                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 classKotlinMetadataAnnotation = containingClass?.annotations?.find {
-                it.hasQualifiedName(KotlinMetadataFqn)
-            } ?: return
-
-            val metadata = KotlinClassMetadata.read(classKotlinMetadataAnnotation.toHeader())
-                ?: return
-
-            // Since we are visiting a function, the null branches shouldn't be called
-            val kmPackage: KmDeclarationContainer = 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
-            }!!
-
-            val kmFunction = kmPackage.findKmFunctionForPsiMethod(this)
-
-            kmFunction?.let {
-                if (it.hasIterableReceiver) {
-                    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() != JavaIterableFQN
-            ) 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
-            )
-        )
-    }
-}
-
-/**
- * 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
-    }
-}
-
-/**
- * @return true if this function is an extension function on Iterable
- */
-private val KmFunction.hasIterableReceiver: Boolean
-    get() = receiverParameterType?.classifier == IterableClassifier
-
-private const val KotlinMetadataFqn = "kotlin.Metadata"
-// Kotlin collections on JVM are just the underlying Java collections
-private const val JavaListFqn = "java.util.List"
-private const val JavaIterableFQN = "java.lang.Iterable"
-private val IterableClassifier = KmClassifier.Class("kotlin/collections/Iterable")
diff --git a/compose/internal-lint-checks/build.gradle b/compose/lint/common/build.gradle
similarity index 69%
copy from compose/internal-lint-checks/build.gradle
copy to compose/lint/common/build.gradle
index b06cfd5..feb7618 100644
--- a/compose/internal-lint-checks/build.gradle
+++ b/compose/lint/common/build.gradle
@@ -18,25 +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"))
 
-    implementation(KOTLIN_METADATA_JVM)
+    api(KOTLIN_METADATA_JVM)
 
     testImplementation(KOTLIN_STDLIB)
     testImplementation(LINT_CORE)
@@ -44,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..66e8fca
--- /dev/null
+++ b/compose/lint/common/src/main/java/androidx/compose/lint/Stubs.kt
@@ -0,0 +1,107 @@
+/*
+ * 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 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 b06cfd5..83bcf45 100644
--- a/compose/internal-lint-checks/build.gradle
+++ b/compose/lint/internal-lint-checks/build.gradle
@@ -35,8 +35,7 @@
     compileOnly(LINT_API_LATEST)
     compileOnly(KOTLIN_STDLIB)
     api(project(":lint-checks"))
-
-    implementation(KOTLIN_METADATA_JVM)
+    implementation(project(":compose:lint:common"))
 
     testImplementation(KOTLIN_STDLIB)
     testImplementation(LINT_CORE)
@@ -44,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 100%
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
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 2cde8e4..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,57 +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 in the version of
-                    // UAST / PSI we are using, so we have to look through the type reference.
-                    // Should be fixed when Lint upgrades the version to 1.4.30+.
-                    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-45307). 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
 
@@ -193,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 =
@@ -217,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/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
similarity index 100%
rename from compose/internal-lint-checks/src/test/java/androidx/compose/lint/ListIteratorDetectorTest.kt
rename to compose/lint/internal-lint-checks/src/test/java/androidx/compose/lint/ListIteratorDetectorTest.kt
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-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/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 @@
-clarabayarri@google.com
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/src/main/AndroidManifest.xml b/compose/material/material/integration-tests/material-studies/src/main/AndroidManifest.xml
deleted file mode 100644
index cba5993..0000000
--- a/compose/material/material/integration-tests/material-studies/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-  ~ 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
-  -->
-
-<manifest
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        package="androidx.compose.material.studies">
-
-    <application>
-        <activity android:name=".rally.RallyActivity"
-                  android:theme="@android:style/Theme.NoTitleBar"
-                  android:configChanges="orientation|screenSize"
-                  android:label="Rally">
-        </activity>
-    </application>
-</manifest>
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(
-            >
-                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(
-            >
-            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(
-            >
-            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(>
-            }
-        }
-    }
-}
-
-/**
- * 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,
-        >
-            // 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,
-        >
-            // 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(
-        >
-        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,
-                     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(
-            >
-            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(
-                        >
-                        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),
-        >
-        background = Color(0xFF26282F),
-        >
-    )
-    // 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),
-        >
-    )
-    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,
-                     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,
-                    >
-                    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/integration-tests/material-studies/src/main/res/values/strings.xml b/compose/material/material/integration-tests/material-studies/src/main/res/values/strings.xml
deleted file mode 100644
index 7df5dd7b..0000000
--- a/compose/material/material/integration-tests/material-studies/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  Copyright 2021 The Android Open Source Project
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-
-<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
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 21bfc21..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
@@ -24,20 +24,18 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
 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.GestureScope
+import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.SemanticsMatcher
 import androidx.compose.ui.test.assert
 import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
 import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
 import androidx.compose.ui.test.assertWidthIsEqualTo
 import androidx.compose.ui.test.bottomCenter
-import androidx.compose.ui.test.center
 import androidx.compose.ui.test.centerLeft
 import androidx.compose.ui.test.click
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -46,9 +44,10 @@
 import androidx.compose.ui.test.performClick
 import androidx.compose.ui.test.performGesture
 import androidx.compose.ui.test.performSemanticsAction
-import androidx.compose.ui.test.swipe
+import androidx.compose.ui.test.swipeDown
 import androidx.compose.ui.test.swipeLeft
 import androidx.compose.ui.test.swipeRight
+import androidx.compose.ui.test.swipeUp
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -61,7 +60,6 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import kotlin.math.roundToInt
 
 @MediumTest
 @RunWith(AndroidJUnit4::class)
@@ -614,6 +612,7 @@
             assertThat(drawerState.currentValue).isEqualTo(BottomDrawerValue.Closed)
         }
 
+        @OptIn(ExperimentalTestApi::class)
         rule.onNodeWithTag(contentTag)
             .performGesture { swipeUp(endY = peekHeight) }
 
@@ -634,6 +633,7 @@
             assertThat(drawerState.currentValue).isEqualTo(BottomDrawerValue.Expanded)
         }
 
+        @OptIn(ExperimentalTestApi::class)
         rule.onNodeWithTag(bottomDrawerTag)
             .performGesture { swipeDown(endY = peekHeight) }
 
@@ -825,55 +825,4 @@
         // Then the drawer should be closed
         rule.onNodeWithTag(bottomDrawerTag).assertTopPositionInRootIsEqualTo(topWhenClosed)
     }
-
-    /**
-     * Performs a swipe up gesture on the associated node. The gesture starts at [startY] and
-     * ends at [endY].
-     *
-     * @param startY The start position of the gesture. By default, this is slightly above the
-     * bottom of the associated node.
-     * @param endY The end position of the gesture. By default, this is the top of the associated
-     * node.
-     */
-    private fun GestureScope.swipeUp(
-        startY: Float = (visibleSize.height * (1 - edgeFuzzFactor)).roundToInt().toFloat(),
-        endY: Float = 0.0f
-    ) {
-        require(startY >= endY) {
-            "Start position $startY needs to be equal or bigger than end position $endY"
-        }
-        val x = center.x
-        val start = Offset(x, startY)
-        val end = Offset(x, endY)
-        swipe(start, end, 200)
-    }
-
-    /**
-     * Performs a swipe down gesture on the associated node. The gesture starts at [startY] and
-     * ends at [endY].
-     *
-     * @param startY The start position of the gesture. By default, this is slightly below the
-     * top of the associated node.
-     * @param endY The end position of the gesture. By default, this is the bottom of the associated
-     * node.
-     */
-    private fun GestureScope.swipeDown(
-        startY: Float = (visibleSize.height * edgeFuzzFactor).roundToInt().toFloat(),
-        endY: Float = visibleSize.height.toFloat()
-    ) {
-        require(endY >= startY) {
-            "End position $endY needs to be equal or bigger than start position $startY"
-        }
-        val x = center.x
-        val start = Offset(x, startY)
-        val end = Offset(x, endY)
-        swipe(start, end, 200)
-    }
-
-    /**
-     * The distance of a swipe's start position from the node's edge, in terms of the node's length.
-     * We do not start the swipe exactly on the node's edge, but somewhat more inward, since swiping
-     * from the exact edge may behave in an unexpected way (e.g. may open a navigation drawer).
-     */
-    private val edgeFuzzFactor = 0.083f
 }
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/runtime/runtime-lint/build.gradle b/compose/runtime/runtime-lint/build.gradle
index 751acb3..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,27 +25,7 @@
     id("kotlin")
 }
 
-// New configuration that allows us to specify a dependency we will include into the resulting
-// jar, since dependencies aren't currently allowed in lint projects included via lintPublish
-// (b/182319899)
-configurations {
-    bundleWithJar
-    testImplementation.extendsFrom bundleWithJar
-    compileOnly.extendsFrom bundleWithJar
-}
-
-jar {
-    dependsOn configurations.bundleWithJar
-    from {
-        configurations.bundleWithJar
-                // The stdlib is already bundled with lint, so no need to include it manually in
-                // the lint.jar
-                .filter( { !(it.name =~ /kotlin-stdlib.*\.jar/ )})
-                .collect {
-                    it.isDirectory() ? it : zipTree(it)
-                }
-    }
-}
+BundleInsideHelper.forInsideLintJar(project)
 
 dependencies {
     // compileOnly because we use lintChecks and it doesn't allow other types of deps
@@ -55,7 +36,7 @@
         compileOnly(LINT_API_MIN)
     }
     compileOnly(KOTLIN_STDLIB)
-    bundleWithJar(KOTLIN_METADATA_JVM)
+    bundleInside(project(":compose:lint:common"))
 
     testImplementation(KOTLIN_STDLIB)
     testImplementation(LINT_CORE)
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
index 2617bdf..28e6b4b 100644
--- 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
@@ -18,6 +18,10 @@
 
 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
@@ -26,13 +30,8 @@
 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 org.jetbrains.uast.UCallExpression
-import org.jetbrains.uast.UElement
-import org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression
-import org.jetbrains.uast.kotlin.KotlinULambdaExpression
-import org.jetbrains.uast.kotlin.declarations.KotlinUMethod
 import java.util.EnumSet
 
 /**
@@ -40,68 +39,19 @@
  * body of a composable function / lambda.
  */
 class ComposableCoroutineCreationDetector : Detector(), SourceCodeScanner {
-    override fun getApplicableMethodNames() = listOf(AsyncShortName, LaunchShortName)
+    override fun getApplicableMethodNames() = listOf(Async.shortName, Launch.shortName)
 
     override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
-        val packageName = (method.containingFile as? PsiJavaFile)?.packageName
-        if (packageName != CoroutinePackageName) return
-        val name = method.name
+        if (!method.isInPackageName(CoroutinePackageName)) return
 
-        var expression: UElement? = node
-
-        // Limit the search depth in case of an error - in most cases the depth should be
-        // fairly shallow unless there are many if / else / while statements.
-        var depth = 0
-
-        // Find the parent function / lambda this call expression is inside
-        while (depth < 10) {
-            expression = expression?.uastParent
-
-            // TODO: this won't handle inline functions, but we also don't know if they are
-            // inline when they are defined in bytecode because this information doesn't
-            // exist in PSI. If this information isn't added to PSI / UAST, we would need to
-            // manually parse the @Metadata annotation.
-            when (expression) {
-                // In the body of a lambda
-                is KotlinULambdaExpression -> {
-                    if (expression.isComposable) {
-                        context.report(
-                            CoroutineCreationDuringComposition,
-                            node,
-                            context.getNameLocation(node),
-                            "Calls to $name should happen inside a LaunchedEffect and " +
-                                "not composition"
-                        )
-                        return
-                    }
-                    val parent = expression.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
-                    }
-                }
-                // In the body of a function
-                is KotlinUMethod -> {
-                    if (expression.hasAnnotation("androidx.compose.runtime.Composable")) {
-                        context.report(
-                            CoroutineCreationDuringComposition,
-                            node,
-                            context.getNameLocation(node),
-                            "Calls to $name should happen inside a LaunchedEffect and " +
-                                "not composition"
-                        )
-                    }
-                    return
-                }
-            }
-            depth++
+        if (node.isInvokedWithinComposable()) {
+            context.report(
+                CoroutineCreationDuringComposition,
+                node,
+                context.getNameLocation(node),
+                "Calls to ${method.name} should happen inside a LaunchedEffect and " +
+                    "not composition"
+            )
         }
     }
 
@@ -125,3 +75,7 @@
         )
     }
 }
+
+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 85272a1..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,22 +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
 
-                // Currently type annotations don't appear on the psiType in the version of
-                // UAST / PSI we are using, so we have to look through the type reference.
-                // Should be fixed when Lint upgrades the version to 1.4.30+.
-                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/Utils.kt b/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/Utils.kt
deleted file mode 100644
index 6d78e36..0000000
--- a/compose/runtime/runtime-lint/src/main/java/androidx/compose/runtime/lint/Utils.kt
+++ /dev/null
@@ -1,249 +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 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.impl.compiled.ClsParameterImpl
-import com.intellij.psi.util.ClassUtil
-import kotlinx.metadata.Flag
-import kotlinx.metadata.KmDeclarationContainer
-import kotlinx.metadata.KmFunction
-import kotlinx.metadata.jvm.KotlinClassHeader
-import kotlinx.metadata.jvm.KotlinClassMetadata
-import kotlinx.metadata.jvm.annotations
-import kotlinx.metadata.jvm.signature
-import org.jetbrains.kotlin.lexer.KtTokens.INLINE_KEYWORD
-import org.jetbrains.kotlin.psi.KtNamedFunction
-import org.jetbrains.kotlin.psi.KtParameter
-import org.jetbrains.kotlin.psi.KtTypeReference
-import org.jetbrains.uast.UAnnotation
-import org.jetbrains.uast.ULambdaExpression
-import org.jetbrains.uast.UMethod
-import org.jetbrains.uast.UParameter
-import org.jetbrains.uast.getContainingUMethod
-import org.jetbrains.uast.getParameterForArgument
-import org.jetbrains.uast.kotlin.AbstractKotlinUVariable
-import org.jetbrains.uast.kotlin.KotlinUFunctionCallExpression
-import org.jetbrains.uast.resolveToUElement
-import org.jetbrains.uast.toUElement
-
-// 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
-/**
- * Returns whether this method is @Composable or not
- */
-@Suppress("DEPRECATION")
-val UMethod.isComposable
-    get() = annotations.any { it.qualifiedName == ComposableFqn }
-
-/**
- * Returns whether this parameter's type is @Composable or not
- */
-val UParameter.isComposable: Boolean
-    get() = when (val source = sourcePsi) {
-        // The parameter is defined in Kotlin source
-        is KtParameter -> source.typeReference!!.isComposable
-        // The parameter is in a class file. Currently type annotations aren't 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 declarationContainer = containingMethod.getKmDeclarationContainer()
-            val kmFunction = declarationContainer?.findKmFunctionForPsiMethod(containingMethod)
-
-            val kmValueParameter = kmFunction?.valueParameters?.find {
-                it.name == name
-            }
-
-            kmValueParameter?.type?.annotations?.find {
-                it.className == KmComposableFqn
-            } != null
-        }
-        // The parameter is in Java source / other, ignore
-        else -> false
-    }
-
-/**
- * Returns whether this type reference is @Composable or not
- */
-val KtTypeReference.isComposable: Boolean
-    // This annotation 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)
-    get() = annotationEntries.any {
-        (it.toUElement() as UAnnotation).qualifiedName == ComposableFqn
-    }
-
-/**
- * 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 KotlinUFunctionCallExpression -> {
-                val parameter = lambdaParent.getParameterForArgument(this) ?: return false
-                if (!(parameter.toUElement() as UParameter).isComposable) return false
-            }
-            // A local / non-local lambda variable
-            is AbstractKotlinUVariable -> {
-                val hasComposableAnnotationOnLambda = findAnnotation(ComposableFqn) != null
-                val hasComposableAnnotationOnType =
-                    (lambdaParent.typeReference?.sourcePsi as? KtTypeReference)
-                        ?.isComposable == true
-
-                if (!hasComposableAnnotationOnLambda && !hasComposableAnnotationOnType) return false
-            }
-            // This probably shouldn't be called, but safe return in case a new UAST type is added
-            // in the future
-            else -> return false
-        }
-        return true
-    }
-
-/**
- * @return whether the resolved declaration for this call expression is an inline function
- */
-val KotlinUFunctionCallExpression.isDeclarationInline: Boolean
-    get() {
-        return when (val source = resolveToUElement()?.sourcePsi) {
-            // Parsing a method defined in a class file
-            is ClsMethodImpl -> {
-                val declarationContainer = source.getKmDeclarationContainer()
-
-                val flags = declarationContainer
-                    ?.findKmFunctionForPsiMethod(source)?.flags ?: return false
-                return Flag.Function.IS_INLINE(flags)
-            }
-            // Parsing a method defined in Kotlin source
-            is KtNamedFunction -> {
-                source.hasModifier(INLINE_KEYWORD)
-            }
-            // Parsing something else (such as a property) which cannot be inline
-            else -> false
-        }
-    }
-
-// 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
- */
-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
-    }
-}
-
-const val RuntimePackageName = "androidx.compose.runtime"
-
-const val ComposableFqn = "$RuntimePackageName.Composable"
-// kotlinx.metadata represents separators as `/` instead of `.`
-val KmComposableFqn get() = ComposableFqn.replace(".", "/")
-
-const val RememberShortName = "remember"
-
-const val CoroutinePackageName = "kotlinx.coroutines"
-const val AsyncShortName = "async"
-const val LaunchShortName = "launch"
-
-private const val KotlinMetadataFqn = "kotlin.Metadata"
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
index bd2f0bb..c3a1f89 100644
--- 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
@@ -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
@@ -40,6 +41,22 @@
     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(
@@ -94,7 +111,7 @@
                 }
             """
             ),
-            composableStub,
+            kotlin(Stubs.Composable),
             coroutineBuildersStub
         )
             .run()
@@ -215,7 +232,7 @@
                 }
             """
             ),
-            composableStub,
+            kotlin(Stubs.Composable),
             coroutineBuildersStub
         )
             .run()
@@ -319,7 +336,7 @@
                 }
             """
             ),
-            composableStub,
+            kotlin(Stubs.Composable),
             coroutineBuildersStub
         )
             .run()
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 83372e1..0000000
--- a/compose/runtime/runtime-lint/src/test/java/androidx/compose/runtime/lint/Stubs.kt
+++ /dev/null
@@ -1,91 +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()
-    """
-)
-
-val coroutineBuildersStub: LintDetectorTest.TestFile = LintDetectorTest.kotlin(
-    """
-        package kotlinx.coroutines
-
-        object CoroutineScope
-
-        fun CoroutineScope.async(
-            block: suspend CoroutineScope.() -> Unit
-        ) {}
-
-        fun CoroutineScope.launch(
-            block: suspend CoroutineScope.() -> Unit
-        ) {}
-    """
-)
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/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/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/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/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/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/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/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/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/inspector/ParameterFactoryTest.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/ParameterFactoryTest.kt
index e1453ec..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
@@ -216,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)
@@ -232,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
@@ -289,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
@@ -421,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")
             }
@@ -438,7 +435,7 @@
         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)
@@ -450,9 +447,10 @@
     fun testLongIntArray() {
         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)
@@ -463,7 +461,7 @@
 
         // 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, "", refToSelf) {
+            parameter("array", ParameterType.Iterable, display, refToSelf, childStartIndex = 5) {
                 parameter("[5]", ParameterType.Int32, 15)
                 parameter("[6]", ParameterType.Int32, 16)
                 parameter("[7]", ParameterType.Int32, 17)
@@ -475,7 +473,7 @@
         // 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)
@@ -486,15 +484,50 @@
 
     @Test
     fun testListWithNullElement() {
-        val value = listOf("Hello", null, "World")
-        val parameter = create("array", value, maxInitialIterableSize = 3)
+        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)
             }
         }
     }
@@ -521,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, "") {
@@ -535,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)
@@ -550,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)
                 }
             }
         }
@@ -574,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)
                 }
             }
         }
@@ -926,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,
@@ -935,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/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 0db476c..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
@@ -432,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)
@@ -451,11 +450,11 @@
             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)
         }
@@ -467,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)
         }
@@ -474,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()
@@ -510,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
@@ -626,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
                 }
@@ -687,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
         }
@@ -711,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
                 }
@@ -736,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))
 
@@ -750,7 +789,7 @@
                     else -> {
                         val elements = parameter.elements
                         modifiers.mapIndexedNotNullTo(elements) { index, element ->
-                            createRecursively("", element, index, elements.size)
+                            createRecursively("", element, index)
                         }
                         parameter.store(value)
                     }
@@ -777,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 6959aa9..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 {
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/src/main/java/androidx/compose/ui/lint/ModifierDeclarationDetector.kt b/compose/ui/ui-lint/src/main/java/androidx/compose/ui/lint/ModifierDeclarationDetector.kt
index c75e458..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
@@ -64,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
 
@@ -207,7 +209,7 @@
                 .name("Add Modifier receiver")
                 .range(context.getLocation(source))
                 .text(name)
-                .with("$ModifierShortName.$name")
+                .with("${Names.Ui.Modifier.shortName}.$name")
                 .autoFix()
                 .build()
         )
@@ -220,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(
@@ -232,7 +234,7 @@
                     .name("Change receiver to Modifier")
                     .range(context.getLocation(source))
                     .text(receiverShortName)
-                    .with(ModifierShortName)
+                    .with(Names.Ui.Modifier.shortName)
                     .autoFix()
                     .build()
             )
@@ -254,7 +256,7 @@
         )
     }
 
-    if (returnType.canonicalText == ModifierFqn) return
+    if (returnType.canonicalText == Names.Ui.Modifier.javaFqn) return
 
     val source = sourcePsi
     if (source is KtCallableDeclaration && source.returnTypeString != null) {
@@ -266,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()
         )
@@ -284,7 +286,7 @@
                     .name("Change return type to Modifier")
                     .range(context.getLocation(this))
                     .text(getterReturnType)
-                    .with(ModifierShortName)
+                    .with(Names.Ui.Modifier.shortName)
                     .autoFix()
                     .build()
             )
@@ -301,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()
             )
@@ -318,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 b28a255..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,10 +152,11 @@
             "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",
+                "\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,
@@ -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 e7c2b4b..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,7 +58,7 @@
                 }
             """
             ),
-            modifierStub
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
@@ -100,7 +101,7 @@
                 val Modifier.fooModifier3: Modifier.Element get() = TestModifier
             """
             ),
-            modifierStub
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
@@ -149,7 +150,7 @@
                 fun Modifier.fooModifier() = TestModifier
             """
             ),
-            modifierStub
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
@@ -184,7 +185,7 @@
                 val Modifier.fooModifier get() = TestModifier
             """
             ),
-            modifierStub
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
@@ -221,7 +222,7 @@
                 }
             """
             ),
-            modifierStub
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
@@ -276,7 +277,7 @@
                 }
             """
             ),
-            modifierStub
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expectClean()
@@ -311,7 +312,7 @@
                 }
             """
             ),
-            modifierStub
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expectClean()
@@ -343,7 +344,7 @@
                 val fooModifier3: Modifier get() = TestModifier
             """
             ),
-            modifierStub
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
@@ -411,7 +412,7 @@
                 val TestModifier.fooModifier3: Modifier get() = TestModifier
             """
             ),
-            modifierStub
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
@@ -487,8 +488,8 @@
                 val Modifier.fooModifier4: Modifier get() = TestModifier(someComposableCall(3))
             """
             ),
-            modifierStub,
-            composableStub
+            kotlin(Stubs.Modifier),
+            kotlin(Stubs.Composable)
         )
             .run()
             .expect(
@@ -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 f2e2aec..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,8 +59,8 @@
                 ) {}
             """
             ),
-            composableStub,
-            modifierStub
+            kotlin(Stubs.Composable),
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
@@ -99,8 +100,8 @@
                 ) {}
             """
             ),
-            composableStub,
-            modifierStub
+            kotlin(Stubs.Composable),
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
@@ -142,8 +143,8 @@
                 ) {}
             """
             ),
-            composableStub,
-            modifierStub
+            kotlin(Stubs.Composable),
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
@@ -183,8 +184,8 @@
                 ) {}
             """
             ),
-            composableStub,
-            modifierStub
+            kotlin(Stubs.Composable),
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
@@ -218,8 +219,8 @@
                 ) {}
             """
             ),
-            composableStub,
-            modifierStub
+            kotlin(Stubs.Composable),
+            kotlin(Stubs.Modifier)
         )
             .run()
             .expect(
@@ -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/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" +
-                "    .    \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/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/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/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/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/androidMain/kotlin/androidx/compose/ui/platform/DisposableSaveableStateRegistry.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/DisposableSaveableStateRegistry.android.kt
index 1515b5d..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,22 +18,18 @@
 
 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
 import androidx.compose.runtime.snapshots.SnapshotMutableState
 import androidx.compose.runtime.structuralEqualityPolicy
-import androidx.compose.ui.util.fastForEachIndexed
 import androidx.savedstate.SavedStateRegistry
 import androidx.savedstate.SavedStateRegistryOwner
 import java.io.Serializable
@@ -118,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?>() ||
@@ -130,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
@@ -165,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
@@ -175,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?>
@@ -183,142 +180,3 @@
     }
     return bundle
 }
-
-private fun wrapMutableStatesIn(list: MutableList<Any?>) {
-    list.fastForEachIndexed { 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.fastForEachIndexed { 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/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/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/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 46079a3..7311bcb 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
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 20a697b..583695e 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")
@@ -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/fragment/OWNERS b/fragment/OWNERS
index f66b82d..f35d2c7 100644
--- a/fragment/OWNERS
+++ b/fragment/OWNERS
@@ -1,3 +1,6 @@
 ilake@google.com
 jbwoods@google.com
-mount@google.com
\ No newline at end of file
+mount@google.com
+
+per-file settings.gradle = dustinlam@google.com, rahulrav@google.com
+
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/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..cef6c82 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;
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..a2c64d9 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());
         }
     }
 
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/lifecycle/OWNERS b/lifecycle/OWNERS
index fc51372..77b5892 100644
--- a/lifecycle/OWNERS
+++ b/lifecycle/OWNERS
@@ -1,2 +1,5 @@
 sergeyv@google.com
-yboyar@google.com
\ No newline at end of file
+yboyar@google.com
+
+per-file settings.gradle = dustinlam@google.com, rahulrav@google.com
+
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)
+            },
+             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) {
+        this@flowWithLifecycle.collect {
+            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 = this@coroutineScope.launch(block = block)
+                            return@LifecycleEventObserver
+                        }
+                        if (event == cancelWorkEvent) {
+                            launchedJob?.cancel()
+                            launchedJob = null
+                        }
+                        if (event == Lifecycle.Event.ON_DESTROY) {
+                            cont.resume(Unit)
+                        }
+                    }
+                    this@repeatOnLifecycle.addObserver(observer as LifecycleEventObserver)
+                }
+            } finally {
+                launchedJob?.cancel()
+                observer?.let {
+                    this@repeatOnLifecycle.removeObserver(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/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/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/navigation/OWNERS b/navigation/OWNERS
index 1dcd21e..5245ec9 100644
--- a/navigation/OWNERS
+++ b/navigation/OWNERS
@@ -2,3 +2,6 @@
 jbwoods@google.com
 danysantiago@google.com
 sergeyv@google.com
+
+per-file settings.gradle = dustinlam@google.com, rahulrav@google.com
+
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..9ae0302
--- /dev/null
+++ b/paging/common/api/public_plus_experimental_3.0.0-beta03.txt
@@ -0,0 +1,501 @@
+// 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();
+  }
+
+  @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/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/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/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/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/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/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/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/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 29b5522..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/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/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/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 a728444..ed5b0c7 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -263,7 +263,9 @@
 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])
@@ -274,7 +276,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])
@@ -324,7 +325,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])
@@ -647,7 +648,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/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/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-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/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-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/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 193d83b..72e825f 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,7 +68,7 @@
 
   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 {
@@ -100,7 +94,7 @@
   }
 
   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 {
@@ -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 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 193d83b..72e825f 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,7 +68,7 @@
 
   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 {
@@ -100,7 +94,7 @@
   }
 
   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 {
@@ -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 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 2584372..96c9f17 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,7 +68,7 @@
 
   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 {
@@ -100,7 +94,7 @@
   }
 
   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 {
@@ -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 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/main/java/androidx/wear/watchface/Complication.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
index 4c55fd3..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
 
@@ -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/ComplicationsManager.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationsManager.kt
index 93c44e30..3007a43 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
@@ -27,11 +27,9 @@
 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 +63,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 +79,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 +91,7 @@
             {
                 InitialComplicationConfig(
                     it.complicationBounds,
-                    it.enabled,
-                    it.supportedTypes,
-                    it.defaultProviderPolicy,
-                    it.defaultProviderType
+                    it.enabled
                 )
             }
         )
@@ -199,12 +191,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 +270,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 +279,7 @@
     @UiThread
     internal fun clearComplicationData() {
         for ((_, complication) in complications) {
-            complication.renderer.clearIdAndData()
+            complication.renderer.setData(null, false)
             (complication.complicationData as MutableObservableWatchData).value =
                 EmptyComplicationData()
         }
@@ -354,8 +343,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 +354,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/Renderer.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Renderer.kt
index a1df4f19..6f8f3db 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. */
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 2c5d45d..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
@@ -544,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
                         )
                     )
                 }
@@ -607,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)
                 }
             }
 
@@ -638,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)
                 }
             }
 
@@ -671,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
                     )
                 }
@@ -688,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) {
@@ -966,8 +955,6 @@
 
             mutableWatchState.isVisible.value = true
             mutableWatchState.isAmbient.value = false
-
-            watchFaceImpl.renderer.onPostCreate()
             return HeadlessWatchFaceImpl(this, uiThreadHandler)
         }
 
@@ -1000,7 +987,6 @@
 
             params.idAndComplicationDataWireFormats?.let { setComplicationDataList(it) }
 
-            watchFaceImpl.renderer.onPostCreate()
             val visibility = pendingVisibilityChanged
             if (visibility != null) {
                 onVisibilityChanged(visibility)
@@ -1068,7 +1054,6 @@
 
                 val watchState = mutableWatchState.asWatchState()
                 createWatchFaceInternal(watchState, surfaceHolder, "maybeCreateWatchFace")
-                watchFaceImpl.renderer.onPostCreate()
 
                 val backgroundAction = pendingBackgroundAction
                 if (backgroundAction != 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 5e7c56a..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
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..a97309a 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 {
@@ -225,6 +227,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..a97309a 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 {
@@ -225,6 +227,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..a97309a 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 {
@@ -225,6 +227,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/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/WebViewFeature.java b/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
index ac14d78..68855c7 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebViewFeature.java
@@ -456,12 +456,9 @@
 
     /**
      * 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();
         }